fontconfig: Branch 'main' - 5 commits

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



 conf.d/48-guessfamily.conf    |  188 ++++++++++++++
 conf.d/49-sansserif.conf      |   96 ++++++-
 conf.d/Makefile.am            |    1 
 conf.d/meson.build            |    1 
 configure.ac                  |    2 
 doc/fontconfig-devel.sgml     |    2 
 doc/fontconfig-user.sgml      |   14 +
 fc-fontations/meson.build     |    4 
 fc-fontations/names.rs        |   36 ++
 fontconfig/fontconfig.h.in    |   17 +
 meson.build                   |    2 
 src/fccfg.c                   |    2 
 src/fcdbg.c                   |  530 ++++++++++++++++++++++++------------------
 src/fcfreetype.c              |   31 ++
 src/fcint.h                   |   44 +++
 src/fcmatch.c                 |    1 
 src/fcname.c                  |   14 +
 src/fcobjs.h                  |    1 
 src/fcxml.c                   |   81 +++++-
 test/test-48-guessfamily.json |   92 +++++++
 test/test-49-sansserif.json   |   66 +++++
 test/test-conf.c              |  107 +++++---
 22 files changed, 1030 insertions(+), 302 deletions(-)

New commits:
commit d2dbd81c37f2ae8da4e779e56758a57172512ad8
Merge: f5eed02 1741ec6
Author: Akira TAGOH <akira@xxxxxxxxx>
Date:   Tue Sep 9 09:43:50 2025 +0000

    Merge branch 'generic-family' into 'main'
    
    Add genericfamily object in FcPattern
    
    Closes #457
    
    See merge request fontconfig/fontconfig!412

commit 1741ec63c007f00b5ba9f3d3773ea7f3cb3c7cba
Author: Akira TAGOH <akira@xxxxxxxxx>
Date:   Tue Sep 2 12:23:10 2025 +0900

    Bump the cache version again
    
    To make sure genericfamily object is available in a cache.

diff --git a/configure.ac b/configure.ac
index 578a894..06dbc57 100644
--- a/configure.ac
+++ b/configure.ac
@@ -77,7 +77,7 @@ AC_DEFINE_UNQUOTED([FC_VERSION_MINOR], [$minor], [minor version])
 AC_DEFINE_UNQUOTED([FC_VERSION_MICRO], [$revision], [revision])
 
 dnl cache version
-CACHE_VERSION=10
+CACHE_VERSION=11
 AC_SUBST(CACHE_VERSION)
 
 dnl libtool versioning
diff --git a/meson.build b/meson.build
index 63a2ca8..4483f6f 100644
--- a/meson.build
+++ b/meson.build
@@ -25,7 +25,7 @@ curversion = fc_version_minor - 1
 libversion = '@0@.@1@.0'.format(soversion, curversion)
 defversion = '@0@.@1@'.format(curversion, fc_version_micro)
 osxversion = curversion + 1
-cacheversion = '10'
+cacheversion = '11'
 
 freetype_req = '>= 21.0.15'
 freetype_req_cmake = '>= 2.8.1'
commit d7299f4d1162858cc4eb7cc74d36ee0df8c0fe8f
Author: Akira TAGOH <akira@xxxxxxxxx>
Date:   Mon Sep 1 23:37:13 2025 +0900

    Get out from FcConfigAdd immediately if no valid pointer given

diff --git a/src/fccfg.c b/src/fccfg.c
index 2e67688..ffd06b0 100644
--- a/src/fccfg.c
+++ b/src/fccfg.c
@@ -1790,6 +1790,8 @@ FcConfigAdd (FcValueListPtr *head,
     FcValueListPtr *prev, l, last;
     FcValueBinding  sameBinding;
 
+    if (!newp)
+	return FcFalse;
     if (position)
 	sameBinding = position->binding;
     else
commit b7f6ac384405be7fedeaac1dd5edafb87026339d
Author: Akira TAGOH <akira@xxxxxxxxx>
Date:   Tue Sep 2 12:00:22 2025 +0900

    Add xsi:nil attribute support to limited elements
    
    This change allows us to make it easy to create a condition
    for missing objects. For example, if one wants to add something
    only when missing family object:
    
    <match>
      <test qual="all" name="family" compare="contains">
        <string xsi:nil="true"/>
      </test>
      <edit name="foo">
        <string>foo</string>
      </edit>
    </match>
    
    $ fc-pattern -c sans
    Pattern has 3 elts (size 16)
            family: "sans"(s)
            lang: "en"(w)
            prgname: "fc-pattern"(s)
    
    $ fc-pattern -c
    Pattern has 3 elts (size 16)
            lang: "en"(w)
            prgname: "fc-pattern"(s)
            foo: "foo"(w)
    
    This will be applied to string, int, double, bool, charset,
    langset, and const only.
    
    Changelog: added

diff --git a/conf.d/48-guessfamily.conf b/conf.d/48-guessfamily.conf
index 3304d7d..dbdabe9 100644
--- a/conf.d/48-guessfamily.conf
+++ b/conf.d/48-guessfamily.conf
@@ -10,6 +10,14 @@
     <edit name="family" mode="append_last">
       <string>monospace</string>
     </edit>
+  </match>
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>mono</string>
+    </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>monospace</const>
     </edit>
@@ -19,9 +27,20 @@
     <test name="family" compare="contains">
       <string>sans</string>
     </test>
+    <test qual="all" name="family" compare="not_contains">
+      <string>mono</string>
+    </test>
     <edit name="family" mode="append_last">
       <string>sans-serif</string>
     </edit>
+  </match>
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>sans</string>
+    </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>sans-serif</const>
     </edit>
@@ -31,9 +50,23 @@
     <test name="family" compare="contains">
       <string>serif</string>
     </test>
+    <test qual="all" name="family" compare="not_eq">
+      <string>sans-serif</string>
+    </test>
     <edit name="family" mode="append_last">
       <string>serif</string>
     </edit>
+  </match>
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>serif</string>
+    </test>
+    <test qual="all" name="family" compare="not_eq">
+      <string>sans-serif</string>
+    </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>serif</const>
     </edit>
@@ -46,6 +79,14 @@
     <edit name="family" mode="append_last">
       <string>emoji</string>
     </edit>
+  </match>
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>emoji</string>
+    </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>emoji</const>
     </edit>
@@ -58,6 +99,14 @@
     <edit name="family" mode="append_last">
       <string>math</string>
     </edit>
+  </match>
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>math</string>
+    </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>math</const>
     </edit>
@@ -67,6 +116,9 @@
     <test name="family" compare="contains">
       <string>system-ui</string>
     </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>system-ui</const>
     </edit>
@@ -76,6 +128,9 @@
     <test name="family" compare="contains">
       <string>cursive</string>
     </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>cursive</const>
     </edit>
@@ -85,6 +140,9 @@
     <test name="family" compare="contains">
       <string>fantasy</string>
     </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>fantasy</const>
     </edit>
@@ -94,6 +152,9 @@
     <test name="family" compare="contains">
       <string>ui-sans-serif</string>
     </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>ui-sans-serif</const>
     </edit>
@@ -103,6 +164,9 @@
     <test name="family" compare="contains">
       <string>ui-serif</string>
     </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>ui-serif</const>
     </edit>
@@ -112,6 +176,9 @@
     <test name="family" compare="contains">
       <string>ui-monospace</string>
     </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>ui-monospace</const>
     </edit>
@@ -121,6 +188,9 @@
     <test name="family" compare="contains">
       <string>ui-rounded</string>
     </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>ui-rounded</const>
     </edit>
@@ -130,6 +200,9 @@
     <test name="family" compare="contains">
       <string>fangsong</string>
     </test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true"/>
+    </test>
     <edit name="genericfamily" mode="append">
       <const>fangsong</const>
     </edit>
diff --git a/conf.d/49-sansserif.conf b/conf.d/49-sansserif.conf
index db040e1..dac60d5 100644
--- a/conf.d/49-sansserif.conf
+++ b/conf.d/49-sansserif.conf
@@ -48,6 +48,24 @@
   <!--
       If the font still has no generic name, add sans-serif
   -->
+  <match target="pattern">
+	<test qual="all" name="family" compare="not_eq">
+	  <string>sans-serif</string>
+	</test>
+	<test qual="all" name="family" compare="not_eq">
+	  <string>serif</string>
+	</test>
+	<test qual="all" name="family" compare="not_eq">
+	  <string>monospace</string>
+	</test>
+    <test qual="all" name="genericfamily" compare="contains">
+      <const xsi:nil="true" />
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>sans-serif</const>
+    </edit>
+  </match>
+
   <match target="pattern">
 	<test qual="all" name="family" compare="not_eq">
 	  <string>sans-serif</string>
diff --git a/src/fcxml.c b/src/fcxml.c
index 2db7578..edb5e8f 100644
--- a/src/fcxml.c
+++ b/src/fcxml.c
@@ -102,6 +102,16 @@ FcRuleDestroy (FcRule *rule)
 	FcRuleDestroy (n);
 }
 
+static FcExpr *
+FcExprCreateNil (FcConfig *config)
+{
+    FcExpr *e = FcConfigAllocExpr (config);
+    if (e) {
+        e->op = FcOpNil;
+    }
+    return e;
+}
+
 static FcExpr *
 FcExprCreateInteger (FcConfig *config, int i)
 {
@@ -530,7 +540,8 @@ typedef enum _FcVStackTag {
 
     FcVStackTest,
     FcVStackExpr,
-    FcVStackEdit
+    FcVStackEdit,
+    FcVStackNil,
 } FcVStackTag;
 
 typedef struct _FcVStack {
@@ -580,6 +591,9 @@ typedef enum _FcConfigSeverity {
     FcSevereError
 } FcConfigSeverity;
 
+static FcBool
+FcConfigLexBool (FcConfigParse *parse, const FcChar8 *bool_);
+
 static void
 FcConfigMessage (FcConfigParse *parse, FcConfigSeverity severe, const char *fmt, ...)
 {
@@ -858,6 +872,18 @@ FcVStackCreateAndPush (FcConfigParse *parse)
     return newp;
 }
 
+static FcBool
+FcVStackPushNil (FcConfigParse *parse)
+{
+    FcVStack *vstack = FcVStackCreateAndPush (parse);
+
+    if (!vstack)
+        return FcFalse;
+    vstack->tag = FcVStackNil;
+
+    return FcTrue;
+}
+
 static FcBool
 FcVStackPushString (FcConfigParse *parse, FcVStackTag tag, FcChar8 *string)
 {
@@ -1054,6 +1080,7 @@ FcVStackPopAndDestroy (FcConfigParse *parse)
 	break;
     case FcVStackInteger:
     case FcVStackDouble:
+    case FcVStackNil:
 	break;
     case FcVStackMatrix:
 	FcExprMatrixFreeShallow (vstack->u.matrix);
@@ -1407,6 +1434,23 @@ FcParseRescan (FcConfigParse *parse)
     }
 }
 
+static FcBool
+FcParseNil (FcConfigParse *parse)
+{
+    const FcChar8 *nil;
+
+    if (!parse->pstack)
+	return FcFalse;
+    nil = FcConfigGetAttribute (parse, "xsi:nil");
+    if (!nil)
+	return FcFalse;
+    if (!FcConfigLexBool (parse, nil))
+	return FcFalse;
+    FcVStackPushNil (parse);
+
+    return FcTrue;
+}
+
 static void
 FcParseInt (FcConfigParse *parse)
 {
@@ -1420,12 +1464,15 @@ FcParseInt (FcConfigParse *parse)
 	FcConfigMessage (parse, FcSevereError, "out of memory");
 	return;
     }
+    if (FcParseNil (parse))
+        goto bail;
     end = 0;
     l = (int)strtol ((char *)s, (char **)&end, 0);
     if (end != s + strlen ((char *)s))
 	FcConfigMessage (parse, FcSevereError, "\"%s\": not a valid integer", s);
     else
 	FcVStackPushInteger (parse, l);
+ bail:
     FcStrBufDestroy (&parse->pstack->str);
 }
 
@@ -1506,12 +1553,15 @@ FcParseDouble (FcConfigParse *parse)
 	FcConfigMessage (parse, FcSevereError, "out of memory");
 	return;
     }
+    if (FcParseNil (parse))
+        goto bail;
     end = 0;
     d = FcStrtod ((char *)s, (char **)&end);
     if (end != s + strlen ((char *)s))
 	FcConfigMessage (parse, FcSevereError, "\"%s\": not a valid double", s);
     else
 	FcVStackPushDouble (parse, d);
+ bail:
     FcStrBufDestroy (&parse->pstack->str);
 }
 
@@ -1527,6 +1577,10 @@ FcParseString (FcConfigParse *parse, FcVStackTag tag)
 	FcConfigMessage (parse, FcSevereError, "out of memory");
 	return;
     }
+    if (FcParseNil (parse)) {
+        FcStrFree (s);
+        return;
+    }
     if (!FcVStackPushString (parse, tag, s))
 	FcStrFree (s);
 }
@@ -1666,12 +1720,15 @@ FcParseBool (FcConfigParse *parse)
 
     if (!parse->pstack)
 	return;
+    if (FcParseNil (parse))
+        goto bail;
     s = FcStrBufDoneStatic (&parse->pstack->str);
     if (!s) {
 	FcConfigMessage (parse, FcSevereError, "out of memory");
 	return;
     }
     FcVStackPushBool (parse, FcConfigLexBool (parse, s));
+ bail:
     FcStrBufDestroy (&parse->pstack->str);
 }
 
@@ -1683,6 +1740,8 @@ FcParseCharSet (FcConfigParse *parse)
     FcChar32   i, begin, end;
     int        n = 0;
 
+    if (FcParseNil (parse))
+        goto bail;
     while ((vstack = FcVStackPeek (parse))) {
 	switch ((int)vstack->tag) {
 	case FcVStackInteger:
@@ -1710,6 +1769,7 @@ FcParseCharSet (FcConfigParse *parse)
 	}
 	FcVStackPopAndDestroy (parse);
     }
+ bail:
     if (n > 0)
 	FcVStackPushCharSet (parse, charset);
     else
@@ -1723,6 +1783,8 @@ FcParseLangSet (FcConfigParse *parse)
     FcLangSet *langset = FcLangSetCreate();
     int        n = 0;
 
+    if (FcParseNil (parse))
+        goto bail;
     while ((vstack = FcVStackPeek (parse))) {
 	switch ((int)vstack->tag) {
 	case FcVStackString:
@@ -1737,6 +1799,7 @@ FcParseLangSet (FcConfigParse *parse)
 	}
 	FcVStackPopAndDestroy (parse);
     }
+ bail:
     if (n > 0)
 	FcVStackPushLangSet (parse, langset);
     else
@@ -2088,6 +2151,9 @@ FcPopExpr (FcConfigParse *parse)
 	break;
     case FcVStackEdit:
 	break;
+    case FcVStackNil:
+        expr = FcExprCreateNil (parse->config);
+        break;
     default:
 	break;
     }
@@ -2322,8 +2388,7 @@ FcParseInclude (FcConfigParse *parse)
 	goto bail;
     }
     attr = FcConfigGetAttribute (parse, "ignore_missing");
-    if (attr && FcConfigLexBool (parse, (FcChar8 *)attr) == FcTrue)
-	ignore_missing = FcTrue;
+    ignore_missing = attr ? FcConfigLexBool (parse, (FcChar8 *)attr) : FcFalse;
     /* deprecated attribute has ever been used to mark
      * old configuration path as deprecated.
      * We don't have any code for it but just keep it for
@@ -2480,15 +2545,9 @@ FcParseTest (FcConfigParse *parse)
     }
     iblanks_string = FcConfigGetAttribute (parse, "ignore-blanks");
     if (iblanks_string) {
-	FcBool f = FcFalse;
-
-	if (!FcNameBool (iblanks_string, &f)) {
-	    FcConfigMessage (parse,
-	                     FcSevereWarning,
-	                     "invalid test ignore-blanks \"%s\"", iblanks_string);
-	}
-	if (f)
+        if (FcConfigLexBool (parse, iblanks_string)) {
 	    flags |= FcOpFlagIgnoreBlanks;
+        }
     }
     expr = FcPopBinary (parse, FcOpComma);
     if (!expr) {
commit 21f65e1c4eac5ea215f5438cd1d0cd28a89f37cf
Author: Akira TAGOH <akira@xxxxxxxxx>
Date:   Mon Sep 1 17:42:46 2025 +0900

    Add genericfamily object in FcPattern
    
    This aims to filter out easily with generic-family and
    avoid misselection for unknown-classified family name.
    We still need a generic-family in family object to
    struct a font list though, the change in 49-sansserif.conf
    tries to complement family from genericfamily object.
    
    Fixes https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/457
    
    Changelog: added

diff --git a/conf.d/48-guessfamily.conf b/conf.d/48-guessfamily.conf
index aaf8527..3304d7d 100644
--- a/conf.d/48-guessfamily.conf
+++ b/conf.d/48-guessfamily.conf
@@ -2,43 +2,136 @@
 <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
 <fontconfig>
   <description>Guess a generic-family for substitution</description>
-  <!-- sans-serif -->
+  <!-- monospace -->
   <match target="pattern">
-    <test qual="all" name="family" compare="not_eq">
-      <string>sans-serif</string>
+    <test name="family" compare="contains">
+      <string>mono</string>
     </test>
+    <edit name="family" mode="append_last">
+      <string>monospace</string>
+    </edit>
+    <edit name="genericfamily" mode="append">
+      <const>monospace</const>
+    </edit>
+  </match>
+  <!-- sans-serif -->
+  <match target="pattern">
     <test name="family" compare="contains">
       <string>sans</string>
     </test>
     <edit name="family" mode="append_last">
       <string>sans-serif</string>
     </edit>
+    <edit name="genericfamily" mode="append">
+      <const>sans-serif</const>
+    </edit>
   </match>
   <!-- serif -->
   <match target="pattern">
-    <test qual="all" name="family" compare="not_eq">
-      <string>sans-serif</string>
-    </test>
-    <test qual="all" name="family" compare="not_eq">
-      <string>serif</string>
-    </test>
     <test name="family" compare="contains">
       <string>serif</string>
     </test>
     <edit name="family" mode="append_last">
       <string>serif</string>
     </edit>
+    <edit name="genericfamily" mode="append">
+      <const>serif</const>
+    </edit>
   </match>
-  <!-- monospace -->
+  <!-- emoji -->
   <match target="pattern">
-    <test qual="all" name="family" compare="not_eq">
-      <string>monospace</string>
+    <test name="family" compare="contains">
+      <string>emoji</string>
     </test>
+    <edit name="family" mode="append_last">
+      <string>emoji</string>
+    </edit>
+    <edit name="genericfamily" mode="append">
+      <const>emoji</const>
+    </edit>
+  </match>
+  <!-- math -->
+  <match target="pattern">
     <test name="family" compare="contains">
-      <string>mono</string>
+      <string>math</string>
     </test>
     <edit name="family" mode="append_last">
-      <string>monospace</string>
+      <string>math</string>
+    </edit>
+    <edit name="genericfamily" mode="append">
+      <const>math</const>
+    </edit>
+  </match>
+  <!-- system-ui -->
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>system-ui</string>
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>system-ui</const>
+    </edit>
+  </match>
+  <!-- cursive -->
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>cursive</string>
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>cursive</const>
+    </edit>
+  </match>
+  <!-- fantasy -->
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>fantasy</string>
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>fantasy</const>
+    </edit>
+  </match>
+  <!-- ui-sans-serif -->
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>ui-sans-serif</string>
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>ui-sans-serif</const>
+    </edit>
+  </match>
+  <!-- ui-serif -->
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>ui-serif</string>
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>ui-serif</const>
+    </edit>
+  </match>
+  <!-- ui-monospace -->
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>ui-monospace</string>
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>ui-monospace</const>
+    </edit>
+  </match>
+  <!-- ui-rounded -->
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>ui-rounded</string>
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>ui-rounded</const>
+    </edit>
+  </match>
+  <!-- fangsong -->
+  <match target="pattern">
+    <test name="family" compare="contains">
+      <string>fangsong</string>
+    </test>
+    <edit name="genericfamily" mode="append">
+      <const>fangsong</const>
     </edit>
   </match>
 </fontconfig>
diff --git a/conf.d/49-sansserif.conf b/conf.d/49-sansserif.conf
index 6cc3a1c..db040e1 100644
--- a/conf.d/49-sansserif.conf
+++ b/conf.d/49-sansserif.conf
@@ -2,21 +2,65 @@
 <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
 <fontconfig>
   <description>Add sans-serif to the family when no generic name</description>
-<!--
-  If the font still has no generic name, add sans-serif
- -->
-	<match target="pattern">
-		<test qual="all" name="family" compare="not_eq">
-			<string>sans-serif</string>
-		</test>
-		<test qual="all" name="family" compare="not_eq">
-			<string>serif</string>
-		</test>
-		<test qual="all" name="family" compare="not_eq">
-			<string>monospace</string>
-		</test>
-		<edit name="family" mode="append_last">
-			<string>sans-serif</string>
-		</edit>
-	</match>
+
+  <!-- Add family from genericfamly -->
+  <match target="pattern">
+    <test name="genericfamily" compare="eq">
+      <const>serif</const>
+    </test>
+    <edit name="family" mode="prepend">
+      <string>serif</string>
+    </edit>
+  </match>
+  <match target="pattern">
+    <test name="genericfamily" compare="eq">
+      <const>sans-serif</const>
+    </test>
+    <edit name="family" mode="prepend">
+      <string>sans-serif</string>
+    </edit>
+  </match>
+  <match target="pattern">
+    <test name="genericfamily" compare="eq">
+      <const>monospace</const>
+    </test>
+    <edit name="family" mode="prepend">
+      <string>monospace</string>
+    </edit>
+  </match>
+  <match target="pattern">
+    <test name="genericfamily" compare="eq">
+      <const>emoji</const>
+    </test>
+    <edit name="family" mode="prepend">
+      <string>emoji</string>
+    </edit>
+  </match>
+  <match target="pattern">
+    <test name="genericfamily" compare="eq">
+      <const>math</const>
+    </test>
+    <edit name="family" mode="prepend">
+      <string>math</string>
+    </edit>
+  </match>
+
+  <!--
+      If the font still has no generic name, add sans-serif
+  -->
+  <match target="pattern">
+	<test qual="all" name="family" compare="not_eq">
+	  <string>sans-serif</string>
+	</test>
+	<test qual="all" name="family" compare="not_eq">
+	  <string>serif</string>
+	</test>
+	<test qual="all" name="family" compare="not_eq">
+	  <string>monospace</string>
+	</test>
+	<edit name="family" mode="append_last">
+	  <string>sans-serif</string>
+	</edit>
+  </match>
+
 </fontconfig>
diff --git a/conf.d/Makefile.am b/conf.d/Makefile.am
index 6a27cf6..bae53d7 100644
--- a/conf.d/Makefile.am
+++ b/conf.d/Makefile.am
@@ -40,6 +40,7 @@ CONF_LINKS = \
 	40-nonlatin.conf \
 	45-generic.conf \
 	45-latin.conf \
+	48-guessfamily.conf \
 	48-spacing.conf \
 	49-sansserif.conf \
 	50-user.conf \
diff --git a/conf.d/meson.build b/conf.d/meson.build
index 5d15532..3656705 100644
--- a/conf.d/meson.build
+++ b/conf.d/meson.build
@@ -57,6 +57,7 @@ conf_links = [
   '40-nonlatin.conf',
   '45-generic.conf',
   '45-latin.conf',
+  '48-guessfamily.conf',
   '48-spacing.conf',
   '49-sansserif.conf',
   '50-user.conf',
diff --git a/doc/fontconfig-devel.sgml b/doc/fontconfig-devel.sgml
index 30b8b40..0c854d6 100644
--- a/doc/fontconfig-devel.sgml
+++ b/doc/fontconfig-devel.sgml
@@ -218,6 +218,8 @@ convenience for the application's rendering mechanism.
     desktop        FC_DESKTOP_NAME        String  Current desktop name
     namedinstance  FC_NAMED_INSTANCE      Bool    Whether font is a named instance
     fontwrapper    FC_FONT_WRAPPER        String  The font wrapper format
+    genericfamily  FC_GENERIC_FAMILY      Int     Type of Generic Family corresponding
+                                                  to FC_FAMILY_* constants
     </programlisting>
   </sect2>
 </sect1>
diff --git a/doc/fontconfig-user.sgml b/doc/fontconfig-user.sgml
index 853ead1..60dd337 100644
--- a/doc/fontconfig-user.sgml
+++ b/doc/fontconfig-user.sgml
@@ -152,6 +152,7 @@ namedinstance   Bool    Whether font is a named instance
 fontwrapper     String  The font wrapper format, current values are WOFF, WOFF2,
                         SFNT for any other SFNT font, and CFF for standalone
                         CFF fonts.
+genericfamily   Int     Type of Generic Family
     </programlisting>
   </refsect2>
   <refsect2>
@@ -563,6 +564,19 @@ hintnone        hintstyle       0
 hintslight      hintstyle       1
 hintmedium      hintstyle       2
 hintfull        hintstyle       3
+serif           genericfamily   1
+sans-serif      genericfamily   2
+monospace       genericfamily   3
+cursive         genericfamily   4
+fantasy         genericfamily   5
+system-ui       genericfamily   6
+ui-serif        genericfamily   7
+ui-sans-serif   genericfamily   8
+ui-monospace    genericfamily   9
+ui-rounded      genericfamily   10
+emoji           genericfamily   11
+math            genericfamily   12
+fangsong        genericfamily   13
     </programlisting>
       </para>
     </refsect2>
diff --git a/fc-fontations/meson.build b/fc-fontations/meson.build
index 116cad8..b816edd 100644
--- a/fc-fontations/meson.build
+++ b/fc-fontations/meson.build
@@ -9,7 +9,7 @@ if (fontations.enabled())
     include_directories: incbase,
     args: [
       '--merge-extern-blocks',
-      '--allowlist-item=(FcCharSet.*|FC_(SLANT|WEIGHT|WIDTH)_.*|FcFontSet(Add|Create|Destroy).*|FcLangSet(Create|Destroy|Copy|Add|HasLang)|FcWeightFromOpenType.*|FC_DUAL|FC_MONO)',
+      '--allowlist-item=(FcCharSet.*|FC_(SLANT|WEIGHT|WIDTH)_.*|FcFontSet(Add|Create|Destroy).*|FcLangSet(Create|Destroy|Copy|Add|HasLang)|FcWeightFromOpenType.*|FC_DUAL|FC_MONO|FC_FAMILY_(UNKNOWN|SANS|SERIF|MONO|EMOJI|MATH))',
       '--raw-line=#![allow(nonstandard_style,unused)]',
     ],
     # FC_NO_MT=1 is added here to reduce required headers in bindings generation.
@@ -73,4 +73,4 @@ if (fontations.enabled())
 
   )
 
-endif
\ No newline at end of file
+endif
diff --git a/fc-fontations/names.rs b/fc-fontations/names.rs
index c390434..ff42278 100644
--- a/fc-fontations/names.rs
+++ b/fc-fontations/names.rs
@@ -27,7 +27,12 @@ use skrifa::{string::StringId, MetadataProvider};
 
 use fcint_bindings::{
     FC_FAMILYLANG_OBJECT, FC_FAMILY_OBJECT, FC_FULLNAMELANG_OBJECT, FC_FULLNAME_OBJECT,
-    FC_INVALID_OBJECT, FC_POSTSCRIPT_NAME_OBJECT, FC_STYLELANG_OBJECT, FC_STYLE_OBJECT,
+    FC_GENERIC_FAMILY_OBJECT, FC_INVALID_OBJECT, FC_POSTSCRIPT_NAME_OBJECT, FC_STYLELANG_OBJECT,
+    FC_STYLE_OBJECT,
+};
+use fontconfig_bindings::{
+    FC_FAMILY_EMOJI, FC_FAMILY_MATH, FC_FAMILY_MONO, FC_FAMILY_SANS, FC_FAMILY_SERIF,
+    FC_FAMILY_UNKNOWN,
 };
 
 use crate::{name_records::FcSortedNameRecords, FcPatternBuilder, InstanceMode, PatternElement};
@@ -138,6 +143,23 @@ fn mangle_full_name_for_named_instance(font: &FontRef, named_instance_id: i32) -
     CString::new(full_name + &subfam).ok()
 }
 
+fn get_generic_family(s: &str) -> i32 {
+    [
+        ("mono", FC_FAMILY_MONO),
+        ("sans", FC_FAMILY_SANS),
+        ("serif", FC_FAMILY_SERIF),
+        ("emoji", FC_FAMILY_EMOJI),
+        ("math", FC_FAMILY_MATH),
+    ]
+    .into_iter()
+    .find_map(|(font_sub_name, generic_family_id)| {
+        s.to_lowercase()
+            .contains(font_sub_name)
+            .then_some(generic_family_id)
+    })
+    .unwrap_or(FC_FAMILY_UNKNOWN) as i32
+}
+
 pub fn add_names(font: &FontRef, instance_mode: InstanceMode, pattern: &mut FcPatternBuilder) {
     let mut already_encountered_names: HashSet<(i32, String)> = HashSet::new();
     let name_table = font.name();
@@ -145,6 +167,7 @@ pub fn add_names(font: &FontRef, instance_mode: InstanceMode, pattern: &mut FcPa
         return;
     }
     let name_table = name_table.unwrap();
+    let mut generic_family = FC_FAMILY_UNKNOWN as i32;
 
     for name_record in FcSortedNameRecords::new(&name_table) {
         let string_id = name_record.name_id();
@@ -193,6 +216,13 @@ pub fn add_names(font: &FontRef, instance_mode: InstanceMode, pattern: &mut FcPa
                 _ => name,
             };
 
+            if object_ids.0 == FC_FAMILY_OBJECT as i32 {
+                if let Some(s) = &name {
+                    if generic_family == FC_FAMILY_UNKNOWN as i32 {
+                        generic_family = get_generic_family(s.as_c_str().to_str().unwrap());
+                    }
+                }
+            }
             if let (Some(name), Some(language)) = (name, language) {
                 let normalized_name = normalize_name(&name);
                 if already_encountered_names.contains(&(object_ids.0, normalized_name.clone())) {
@@ -207,4 +237,8 @@ pub fn add_names(font: &FontRef, instance_mode: InstanceMode, pattern: &mut FcPa
             }
         }
     }
+    pattern.append_element(PatternElement::new(
+        FC_GENERIC_FAMILY_OBJECT as i32,
+        generic_family.into(),
+    ));
 }
diff --git a/fontconfig/fontconfig.h.in b/fontconfig/fontconfig.h.in
index e8944f9..775a6bf 100644
--- a/fontconfig/fontconfig.h.in
+++ b/fontconfig/fontconfig.h.in
@@ -133,6 +133,7 @@ typedef int            FcBool;
 #define FC_DESKTOP_NAME         "desktop"        /* String */
 #define FC_NAMED_INSTANCE       "namedinstance"  /* Bool - true if font is named instance */
 #define FC_FONT_WRAPPER         "fontwrapper"    /* String */
+#define FC_GENERIC_FAMILY       "genericfamily"  /* Integer */
 
 #define FC_CACHE_SUFFIX         ".cache-" FC_CACHE_VERSION
 #define FC_DIR_CACHE_FILE       "fonts.cache-" FC_CACHE_VERSION
@@ -203,6 +204,22 @@ typedef int            FcBool;
 #define FC_LCD_LIGHT            2
 #define FC_LCD_LEGACY           3
 
+/* Generic family */
+#define FC_FAMILY_UNKNOWN       0
+#define FC_FAMILY_SERIF         1
+#define FC_FAMILY_SANS          2
+#define FC_FAMILY_MONO          3
+#define FC_FAMILY_CURSIVE       4
+#define FC_FAMILY_FANTASY       5
+#define FC_FAMILY_SYSTEM_UI     6
+#define FC_FAMILY_UI_SERIF      7
+#define FC_FAMILY_UI_SANS       8
+#define FC_FAMILY_UI_MONO       9
+#define FC_FAMILY_UI_ROUNDED    10
+#define FC_FAMILY_EMOJI         11
+#define FC_FAMILY_MATH          12
+#define FC_FAMILY_FANGSONG      13
+
 typedef enum _FcType {
     FcTypeUnknown = -1,
     FcTypeVoid,
diff --git a/src/fcdbg.c b/src/fcdbg.c
index 97e5669..b0e342d 100644
--- a/src/fcdbg.c
+++ b/src/fcdbg.c
@@ -28,53 +28,52 @@
 #include <stdlib.h>
 
 static void
-_FcValuePrintFile (FILE *f, const FcValue v)
+_FcValuePrintFile (FILE *stream, const FcValue v)
 {
     switch (v.type) {
     case FcTypeUnknown:
-	fprintf (f, "<unknown>");
+	fprintf (stream, "<unknown>");
 	break;
     case FcTypeVoid:
-	fprintf (f, "<void>");
+	fprintf (stream, "<void>");
 	break;
     case FcTypeInteger:
-	fprintf (f, "%d(i)", v.u.i);
+	fprintf (stream, "%d(i)", v.u.i);
 	break;
     case FcTypeDouble:
-	fprintf (f, "%g(f)", v.u.d);
+	fprintf (stream, "%g(f)", v.u.d);
 	break;
     case FcTypeString:
-	fprintf (f, "\"%s\"", v.u.s);
+	fprintf (stream, "\"%s\"", v.u.s);
 	break;
     case FcTypeBool:
-	fprintf (f,
+	fprintf (stream,
 	         v.u.b == FcTrue ? "True" : v.u.b == FcFalse ? "False"
 	                                                     : "DontCare");
 	break;
     case FcTypeMatrix:
-	fprintf (f, "[%g %g; %g %g]", v.u.m->xx, v.u.m->xy, v.u.m->yx, v.u.m->yy);
+	fprintf (stream, "[%g %g; %g %g]", v.u.m->xx, v.u.m->xy, v.u.m->yx, v.u.m->yy);
 	break;
-    case FcTypeCharSet: /* XXX */
-	if (f == stdout)
-	    FcCharSetPrint (v.u.c);
+    case FcTypeCharSet:
+	FcCharSetPrintFile (stream, v.u.c);
 	break;
     case FcTypeLangSet:
 	FcLangSetPrint (v.u.l);
 	break;
     case FcTypeFTFace:
-	fprintf (f, "face");
+	fprintf (stream, "face");
 	break;
     case FcTypeRange:
-	fprintf (f, "[%g %g]", v.u.r->begin, v.u.r->end);
+	fprintf (stream, "[%g %g]", v.u.r->begin, v.u.r->end);
 	break;
     }
 }
 
 void
-FcValuePrintFile (FILE *f, const FcValue v)
+FcValuePrintFile (FILE *stream, const FcValue v)
 {
-    fprintf (f, " ");
-    _FcValuePrintFile (f, v);
+    fprintf (stream, " ");
+    _FcValuePrintFile (stream, v);
 }
 
 void
@@ -85,132 +84,171 @@ FcValuePrint (const FcValue v)
 }
 
 void
-FcValuePrintWithPosition (const FcValue v, FcBool show_pos_mark)
+FcValuePrintFileWithPosition (FILE *stream, const FcValue v, FcBool show_pos_mark)
 {
     if (show_pos_mark)
-	printf (" [marker] ");
+	fprintf (stream, " [marker] ");
     else
-	printf (" ");
-    _FcValuePrintFile (stdout, v);
+	fprintf (stream, " ");
+    _FcValuePrintFile (stream, v);
+}
+
+void
+FcValuePrintWithPosition (const FcValue v, FcBool show_pos_mark)
+{
+    FcValuePrintFileWithPosition (stdout, v, show_pos_mark);
 }
 
 static void
-FcValueBindingPrint (const FcValueListPtr l)
+FcValueBindingPrintFile (FILE *stream, const FcValueListPtr l)
 {
     switch (l->binding) {
     case FcValueBindingWeak:
-	printf ("(w)");
+	fprintf (stream, "(w)");
 	break;
     case FcValueBindingStrong:
-	printf ("(s)");
+	fprintf (stream, "(s)");
 	break;
     case FcValueBindingSame:
-	printf ("(=)");
+	fprintf (stream, "(=)");
 	break;
     default:
 	/* shouldn't be reached */
-	printf ("(?)");
+	fprintf (stream, "(?)");
 	break;
     }
 }
 
 void
-FcValueListPrintWithPosition (FcValueListPtr l, const FcValueListPtr pos)
+FcValueListPrintFileWithPosition (FILE *stream, FcValueListPtr l, const FcValueListPtr pos)
 {
     for (; l != NULL; l = FcValueListNext (l)) {
-	FcValuePrintWithPosition (FcValueCanonicalize (&l->value), pos != NULL && l == pos);
-	FcValueBindingPrint (l);
+	FcValuePrintFileWithPosition (stream, FcValueCanonicalize (&l->value), pos != NULL && l == pos);
+	FcValueBindingPrintFile (stream, l);
     }
     if (!pos)
-	printf (" [marker]");
+	fprintf (stream, " [marker]");
 }
 
 void
-FcValueListPrint (FcValueListPtr l)
+FcValueListPrintWithPosition (FcValueListPtr l, const FcValueListPtr pos)
+{
+    FcValueListPrintFileWithPosition(stdout, l, pos);
+}
+
+void
+FcValueListPrintFile (FILE *stream, FcValueListPtr l)
 {
     for (; l != NULL; l = FcValueListNext (l)) {
-	FcValuePrint (FcValueCanonicalize (&l->value));
-	FcValueBindingPrint (l);
+	FcValuePrintFile (stream, FcValueCanonicalize (&l->value));
+	FcValueBindingPrintFile (stream, l);
     }
 }
 
 void
-FcLangSetPrint (const FcLangSet *ls)
+FcValueListPrint (FcValueListPtr l)
+{
+    FcValueListPrintFile (stdout, l);
+}
+
+void
+FcLangSetPrintFile (FILE *stream, const FcLangSet *ls)
 {
     FcStrBuf buf;
     FcChar8  init_buf[1024];
 
     FcStrBufInit (&buf, init_buf, sizeof (init_buf));
     if (FcNameUnparseLangSet (&buf, ls) && FcStrBufChar (&buf, '\0'))
-	printf ("%s", buf.buf);
+	fprintf (stream, "%s", buf.buf);
     else
-	printf ("langset (alloc error)");
+	fprintf (stream, "langset (alloc error)");
     FcStrBufDestroy (&buf);
 }
 
 void
-FcCharSetPrint (const FcCharSet *c)
+FcLangSetPrint (const FcLangSet *ls)
+{
+    FcLangSetPrintFile (stdout, ls);
+}
+
+void
+FcCharSetPrintFile (FILE *stream, const FcCharSet *c)
 {
     int       i, j;
     intptr_t *leaves = FcCharSetLeaves (c);
     FcChar16 *numbers = FcCharSetNumbers (c);
 
 #if 0
-    printf ("CharSet  0x%x\n", (intptr_t) c);
-    printf ("Leaves:  +%d = 0x%x\n", c->leaves_offset, (intptr_t) leaves);
-    printf ("Numbers: +%d = 0x%x\n", c->numbers_offset, (intptr_t) numbers);
+    fprintf (stream, "CharSet  0x%x\n", (intptr_t) c);
+    fprintf (stream, "Leaves:  +%d = 0x%x\n", c->leaves_offset, (intptr_t) leaves);
+    fprintf (stream, "Numbers: +%d = 0x%x\n", c->numbers_offset, (intptr_t) numbers);
 
     for (i = 0; i < c->num; i++)
     {
-	printf ("Page %d: %04x +%d = 0x%x\n",
-		i, numbers[i], leaves[i],
-		(intptr_t) FcOffsetToPtr (leaves, leaves[i], FcCharLeaf));
+	fprintf (stream, "Page %d: %04x +%d = 0x%x\n",
+		 i, numbers[i], leaves[i],
+		 (intptr_t) FcOffsetToPtr (leaves, leaves[i], FcCharLeaf));
     }
 #endif
 
-    printf ("\n");
+    fprintf (stream, "\n");
     for (i = 0; i < c->num; i++) {
 	intptr_t    leaf_offset = leaves[i];
 	FcCharLeaf *leaf = FcOffsetToPtr (leaves, leaf_offset, FcCharLeaf);
 
-	printf ("\t");
-	printf ("%04x:", numbers[i]);
+	fprintf (stream, "\t");
+	fprintf (stream, "%04x:", numbers[i]);
 	for (j = 0; j < 256 / 32; j++)
-	    printf (" %08x", leaf->map[j]);
-	printf ("\n");
+	    fprintf (stream, " %08x", leaf->map[j]);
+	fprintf (stream, "\n");
     }
 }
 
 void
-FcPatternPrint (const FcPattern *p)
+FcCharSetPrint (const FcCharSet *c)
+{
+    FcCharSetPrintFile (stdout, c);
+}
+
+void
+FcPatternPrintFile (FILE *stream, const FcPattern *p)
 {
     FcPatternIter iter;
 
     if (!p) {
-	printf ("Null pattern\n");
+	fprintf (stream, "Null pattern\n");
 	return;
     }
-    printf ("Pattern has %d elts (size %d)\n", FcPatternObjectCount (p), p->size);
+    fprintf (stream, "Pattern has %d elts (size %d)\n", FcPatternObjectCount (p), p->size);
     FcPatternIterStart (p, &iter);
     do {
-	printf ("\t%s:", FcPatternIterGetObject (p, &iter));
-	FcValueListPrint (FcPatternIterGetValues (p, &iter));
-	printf ("\n");
+	fprintf (stream, "\t%s:", FcPatternIterGetObject (p, &iter));
+	FcValueListPrintFile (stream, FcPatternIterGetValues (p, &iter));
+	fprintf (stream, "\n");
     } while (FcPatternIterNext (p, &iter));
-    printf ("\n");
+    fprintf (stream, "\n");
 }
 
-#define FcOpFlagsPrint(_o_)             \
-    {                                   \
-	int f = FC_OP_GET_FLAGS (_o_);  \
-	if (f & FcOpFlagIgnoreBlanks)   \
-	    printf ("(ignore blanks)"); \
+void
+FcPatternPrint (const FcPattern *p)
+{
+    FcPatternPrintFile (stdout, p);
+}
+
+#define FcOpFlagsPrintFile(_f_, _o_)          \
+    {                                         \
+	int f = FC_OP_GET_FLAGS (_o_);        \
+	if (f & FcOpFlagIgnoreBlanks)         \
+	    fprintf (_f_, "(ignore blanks)"); \
     }
 
+#define FcOpFlagsPrint(_o_)    FcOpFlagsPrintFile(stdout, _o_)
+
 void
-FcPatternPrint2 (FcPattern         *pp1,
-                 FcPattern         *pp2,
-                 const FcObjectSet *os)
+FcPatternPrint2File (FILE              *stream,
+                     FcPattern         *pp1,
+                     FcPattern         *pp2,
+                     const FcObjectSet *os)
 {
     int           i, j, k, pos;
     FcPatternElt *e1, *e2;
@@ -223,8 +261,8 @@ FcPatternPrint2 (FcPattern         *pp1,
 	p1 = pp1;
 	p2 = pp2;
     }
-    printf ("Pattern has %d elts (size %d), %d elts (size %d)\n",
-            p1->num, p1->size, p2->num, p2->size);
+    fprintf (stream, "Pattern has %d elts (size %d), %d elts (size %d)\n",
+             p1->num, p1->size, p2->num, p2->size);
     for (i = 0, j = 0; i < p1->num; i++) {
 	e1 = &FcPatternElts (p1)[i];
 	e2 = &FcPatternElts (p2)[j];
@@ -233,25 +271,25 @@ FcPatternPrint2 (FcPattern         *pp1,
 	    if (pos >= 0) {
 		for (k = j; k < pos; k++) {
 		    e2 = &FcPatternElts (p2)[k];
-		    printf ("\t%s: (None) -> ", FcObjectName (e2->object));
-		    FcValueListPrint (FcPatternEltValues (e2));
-		    printf ("\n");
+		    fprintf (stream, "\t%s: (None) -> ", FcObjectName (e2->object));
+		    FcValueListPrintFile (stream, FcPatternEltValues (e2));
+		    fprintf (stream, "\n");
 		}
 		j = pos;
 		goto cont;
 	    } else {
-		printf ("\t%s:", FcObjectName (e1->object));
-		FcValueListPrint (FcPatternEltValues (e1));
-		printf (" -> (None)\n");
+		fprintf (stream, "\t%s:", FcObjectName (e1->object));
+		FcValueListPrintFile (stream, FcPatternEltValues (e1));
+		fprintf (stream, " -> (None)\n");
 	    }
 	} else {
 	cont:
-	    printf ("\t%s:", FcObjectName (e1->object));
-	    FcValueListPrint (FcPatternEltValues (e1));
-	    printf (" -> ");
+	    fprintf (stream, "\t%s:", FcObjectName (e1->object));
+	    FcValueListPrintFile (stream, FcPatternEltValues (e1));
+	    fprintf (stream, " -> ");
 	    e2 = &FcPatternElts (p2)[j];
-	    FcValueListPrint (FcPatternEltValues (e2));
-	    printf ("\n");
+	    FcValueListPrintFile (stream, FcPatternEltValues (e2));
+	    fprintf (stream, "\n");
 	    j++;
 	}
     }
@@ -259,9 +297,9 @@ FcPatternPrint2 (FcPattern         *pp1,
 	for (k = j; k < p2->num; k++) {
 	    e2 = &FcPatternElts (p2)[k];
 	    if (FcObjectName (e2->object)) {
-		printf ("\t%s: (None) -> ", FcObjectName (e2->object));
-		FcValueListPrint (FcPatternEltValues (e2));
-		printf ("\n");
+		fprintf (stream, "\t%s: (None) -> ", FcObjectName (e2->object));
+		FcValueListPrintFile (stream, FcPatternEltValues (e2));
+		fprintf (stream, "\n");
 	    }
 	}
     }
@@ -272,115 +310,129 @@ FcPatternPrint2 (FcPattern         *pp1,
 }
 
 void
-FcOpPrint (FcOp op_)
+FcPatternPrint2 (FcPattern         *pp1,
+                 FcPattern         *pp2,
+                 const FcObjectSet *os)
+{
+    FcPatternPrint2File (stdout, pp1, pp2, os);
+}
+
+void
+FcOpPrintFile (FILE *stream, FcOp op_)
 {
     FcOp op = FC_OP_GET_OP (op_);
 
     switch (op) {
-    case FcOpInteger: printf ("Integer"); break;
-    case FcOpDouble: printf ("Double"); break;
-    case FcOpString: printf ("String"); break;
-    case FcOpMatrix: printf ("Matrix"); break;
-    case FcOpRange: printf ("Range"); break;
-    case FcOpBool: printf ("Bool"); break;
-    case FcOpCharSet: printf ("CharSet"); break;
-    case FcOpLangSet: printf ("LangSet"); break;
-    case FcOpField: printf ("Field"); break;
-    case FcOpConst: printf ("Const"); break;
-    case FcOpAssign: printf ("Assign"); break;
-    case FcOpAssignReplace: printf ("AssignReplace"); break;
-    case FcOpPrepend: printf ("Prepend"); break;
-    case FcOpPrependFirst: printf ("PrependFirst"); break;
-    case FcOpAppend: printf ("Append"); break;
-    case FcOpAppendLast: printf ("AppendLast"); break;
-    case FcOpDelete: printf ("Delete"); break;
-    case FcOpDeleteAll: printf ("DeleteAll"); break;
-    case FcOpQuest: printf ("Quest"); break;
-    case FcOpOr: printf ("Or"); break;
-    case FcOpAnd: printf ("And"); break;
+    case FcOpInteger: fprintf (stream, "Integer"); break;
+    case FcOpDouble: fprintf (stream, "Double"); break;
+    case FcOpString: fprintf (stream, "String"); break;
+    case FcOpMatrix: fprintf (stream, "Matrix"); break;
+    case FcOpRange: fprintf (stream, "Range"); break;
+    case FcOpBool: fprintf (stream, "Bool"); break;
+    case FcOpCharSet: fprintf (stream, "CharSet"); break;
+    case FcOpLangSet: fprintf (stream, "LangSet"); break;
+    case FcOpField: fprintf (stream, "Field"); break;
+    case FcOpConst: fprintf (stream, "Const"); break;
+    case FcOpAssign: fprintf (stream, "Assign"); break;
+    case FcOpAssignReplace: fprintf (stream, "AssignReplace"); break;
+    case FcOpPrepend: fprintf (stream, "Prepend"); break;
+    case FcOpPrependFirst: fprintf (stream, "PrependFirst"); break;
+    case FcOpAppend: fprintf (stream, "Append"); break;
+    case FcOpAppendLast: fprintf (stream, "AppendLast"); break;
+    case FcOpDelete: fprintf (stream, "Delete"); break;
+    case FcOpDeleteAll: fprintf (stream, "DeleteAll"); break;
+    case FcOpQuest: fprintf (stream, "Quest"); break;
+    case FcOpOr: fprintf (stream, "Or"); break;
+    case FcOpAnd: fprintf (stream, "And"); break;
     case FcOpEqual:
-	printf ("Equal");
-	FcOpFlagsPrint (op_);
+	fprintf (stream, "Equal");
+	FcOpFlagsPrintFile (stream, op_);
 	break;
     case FcOpNotEqual:
-	printf ("NotEqual");
-	FcOpFlagsPrint (op_);
+	fprintf (stream, "NotEqual");
+	FcOpFlagsPrintFile (stream, op_);
 	break;
-    case FcOpLess: printf ("Less"); break;
-    case FcOpLessEqual: printf ("LessEqual"); break;
-    case FcOpMore: printf ("More"); break;
-    case FcOpMoreEqual: printf ("MoreEqual"); break;
-    case FcOpContains: printf ("Contains"); break;
-    case FcOpNotContains: printf ("NotContains"); break;
-    case FcOpPlus: printf ("Plus"); break;
-    case FcOpMinus: printf ("Minus"); break;
-    case FcOpTimes: printf ("Times"); break;
-    case FcOpDivide: printf ("Divide"); break;
-    case FcOpNot: printf ("Not"); break;
-    case FcOpNil: printf ("Nil"); break;
-    case FcOpComma: printf ("Comma"); break;
-    case FcOpFloor: printf ("Floor"); break;
-    case FcOpCeil: printf ("Ceil"); break;
-    case FcOpRound: printf ("Round"); break;
-    case FcOpTrunc: printf ("Trunc"); break;
+    case FcOpLess: fprintf (stream, "Less"); break;
+    case FcOpLessEqual: fprintf (stream, "LessEqual"); break;
+    case FcOpMore: fprintf (stream, "More"); break;
+    case FcOpMoreEqual: fprintf (stream, "MoreEqual"); break;
+    case FcOpContains: fprintf (stream, "Contains"); break;
+    case FcOpNotContains: fprintf (stream, "NotContains"); break;
+    case FcOpPlus: fprintf (stream, "Plus"); break;
+    case FcOpMinus: fprintf (stream, "Minus"); break;
+    case FcOpTimes: fprintf (stream, "Times"); break;
+    case FcOpDivide: fprintf (stream, "Divide"); break;
+    case FcOpNot: fprintf (stream, "Not"); break;
+    case FcOpNil: fprintf (stream, "Nil"); break;
+    case FcOpComma: fprintf (stream, "Comma"); break;
+    case FcOpFloor: fprintf (stream, "Floor"); break;
+    case FcOpCeil: fprintf (stream, "Ceil"); break;
+    case FcOpRound: fprintf (stream, "Round"); break;
+    case FcOpTrunc: fprintf (stream, "Trunc"); break;
     case FcOpListing:
-	printf ("Listing");
-	FcOpFlagsPrint (op_);
+	fprintf (stream, "Listing");
+	FcOpFlagsPrintFile (stream, op_);
 	break;
-    case FcOpInvalid: printf ("Invalid"); break;
+    case FcOpInvalid: fprintf (stream, "Invalid"); break;
     }
 }
 
 void
-FcExprPrint (const FcExpr *expr)
+FcOpPrint (FcOp op_)
+{
+    FcOpPrintFile (stdout, op_);
+}
+
+void
+FcExprPrintFile (FILE *stream, const FcExpr *expr)
 {
     if (!expr)
-	printf ("none");
+	fprintf (stream, "none");
     else
 	switch (FC_OP_GET_OP (expr->op)) {
-	case FcOpInteger: printf ("%d", expr->u.ival); break;
-	case FcOpDouble: printf ("%g", expr->u.dval); break;
-	case FcOpString: printf ("\"%s\"", expr->u.sval); break;
+	case FcOpInteger: fprintf (stream, "%d", expr->u.ival); break;
+	case FcOpDouble: fprintf (stream, "%g", expr->u.dval); break;
+	case FcOpString: fprintf (stream, "\"%s\"", expr->u.sval); break;
 	case FcOpMatrix:
-	    printf ("[");
-	    FcExprPrint (expr->u.mexpr->xx);
-	    printf (" ");
-	    FcExprPrint (expr->u.mexpr->xy);
-	    printf ("; ");
-	    FcExprPrint (expr->u.mexpr->yx);
-	    printf (" ");
-	    FcExprPrint (expr->u.mexpr->yy);
-	    printf ("]");
+	    fprintf (stream, "[");
+	    FcExprPrintFile (stream, expr->u.mexpr->xx);
+	    fprintf (stream, " ");
+	    FcExprPrintFile (stream, expr->u.mexpr->xy);
+	    fprintf (stream, "; ");
+	    FcExprPrintFile (stream, expr->u.mexpr->yx);
+	    fprintf (stream, " ");
+	    FcExprPrintFile (stream, expr->u.mexpr->yy);
+	    fprintf (stream, "]");
 	    break;
 	case FcOpRange:
-	    printf ("(%g, %g)", expr->u.rval->begin, expr->u.rval->end);
+	    fprintf (stream, "(%g, %g)", expr->u.rval->begin, expr->u.rval->end);
 	    break;
-	case FcOpBool: printf ("%s", expr->u.bval ? "true" : "false"); break;
-	case FcOpCharSet: printf ("charset\n"); break;
+	case FcOpBool: fprintf (stream, "%s", expr->u.bval ? "true" : "false"); break;
+	case FcOpCharSet: fprintf (stream, "charset\n"); break;
 	case FcOpLangSet:
-	    printf ("langset:");
-	    FcLangSetPrint (expr->u.lval);
-	    printf ("\n");
+	    fprintf (stream, "langset:");
+	    FcLangSetPrintFile (stream, expr->u.lval);
+	    fprintf (stream, "\n");
 	    break;
-	case FcOpNil: printf ("nil\n"); break;
+	case FcOpNil: fprintf (stream, "nil\n"); break;
 	case FcOpField:
-	    printf ("%s ", FcObjectName (expr->u.name.object));
+	    fprintf (stream, "%s ", FcObjectName (expr->u.name.object));
 	    switch ((int)expr->u.name.kind) {
 	    case FcMatchPattern:
-		printf ("(pattern) ");
+		fprintf (stream, "(pattern) ");
 		break;
 	    case FcMatchFont:
-		printf ("(font) ");
+		fprintf (stream, "(font) ");
 		break;
 	    }
 	    break;
-	case FcOpConst: printf ("%s", expr->u.constant); break;
+	case FcOpConst: fprintf (stream, "%s", expr->u.constant); break;
 	case FcOpQuest:
-	    FcExprPrint (expr->u.tree.left);
-	    printf (" quest ");
-	    FcExprPrint (expr->u.tree.right->u.tree.left);
-	    printf (" colon ");
-	    FcExprPrint (expr->u.tree.right->u.tree.right);
+	    FcExprPrintFile (stream, expr->u.tree.left);
+	    fprintf (stream, " quest ");
+	    FcExprPrintFile (stream, expr->u.tree.right->u.tree.left);
+	    fprintf (stream, " colon ");
+	    FcExprPrintFile (stream, expr->u.tree.right->u.tree.right);
 	    break;
 	case FcOpAssign:
 	case FcOpAssignReplace:
@@ -404,81 +456,87 @@ FcExprPrint (const FcExpr *expr)
 	case FcOpTimes:
 	case FcOpDivide:
 	case FcOpComma:
-	    FcExprPrint (expr->u.tree.left);
-	    printf (" ");
+	    FcExprPrintFile (stream, expr->u.tree.left);
+	    fprintf (stream, " ");
 	    switch (FC_OP_GET_OP (expr->op)) {
-	    case FcOpAssign: printf ("Assign"); break;
-	    case FcOpAssignReplace: printf ("AssignReplace"); break;
-	    case FcOpPrependFirst: printf ("PrependFirst"); break;
-	    case FcOpPrepend: printf ("Prepend"); break;
-	    case FcOpAppend: printf ("Append"); break;
-	    case FcOpAppendLast: printf ("AppendLast"); break;
-	    case FcOpOr: printf ("Or"); break;
-	    case FcOpAnd: printf ("And"); break;
+	    case FcOpAssign: fprintf (stream, "Assign"); break;
+	    case FcOpAssignReplace: fprintf (stream, "AssignReplace"); break;
+	    case FcOpPrependFirst: fprintf (stream, "PrependFirst"); break;
+	    case FcOpPrepend: fprintf (stream, "Prepend"); break;
+	    case FcOpAppend: fprintf (stream, "Append"); break;
+	    case FcOpAppendLast: fprintf (stream, "AppendLast"); break;
+	    case FcOpOr: fprintf (stream, "Or"); break;
+	    case FcOpAnd: fprintf (stream, "And"); break;
 	    case FcOpEqual:
-		printf ("Equal");
-		FcOpFlagsPrint (expr->op);
+		fprintf (stream, "Equal");
+		FcOpFlagsPrintFile (stream, expr->op);
 		break;
 	    case FcOpNotEqual:
-		printf ("NotEqual");
-		FcOpFlagsPrint (expr->op);
+		fprintf (stream, "NotEqual");
+		FcOpFlagsPrintFile (stream, expr->op);
 		break;
-	    case FcOpLess: printf ("Less"); break;
-	    case FcOpLessEqual: printf ("LessEqual"); break;
-	    case FcOpMore: printf ("More"); break;
-	    case FcOpMoreEqual: printf ("MoreEqual"); break;
-	    case FcOpContains: printf ("Contains"); break;
+	    case FcOpLess: fprintf (stream, "Less"); break;
+	    case FcOpLessEqual: fprintf (stream, "LessEqual"); break;
+	    case FcOpMore: fprintf (stream, "More"); break;
+	    case FcOpMoreEqual: fprintf (stream, "MoreEqual"); break;
+	    case FcOpContains: fprintf (stream, "Contains"); break;
 	    case FcOpListing:
-		printf ("Listing");
-		FcOpFlagsPrint (expr->op);
+		fprintf (stream, "Listing");
+		FcOpFlagsPrintFile (stream, expr->op);
 		break;
-	    case FcOpNotContains: printf ("NotContains"); break;
-	    case FcOpPlus: printf ("Plus"); break;
-	    case FcOpMinus: printf ("Minus"); break;
-	    case FcOpTimes: printf ("Times"); break;
-	    case FcOpDivide: printf ("Divide"); break;
-	    case FcOpComma: printf ("Comma"); break;
+	    case FcOpNotContains: fprintf (stream, "NotContains"); break;
+	    case FcOpPlus: fprintf (stream, "Plus"); break;
+	    case FcOpMinus: fprintf (stream, "Minus"); break;
+	    case FcOpTimes: fprintf (stream, "Times"); break;
+	    case FcOpDivide: fprintf (stream, "Divide"); break;
+	    case FcOpComma: fprintf (stream, "Comma"); break;
 	    default: break;
 	    }
-	    printf (" ");
-	    FcExprPrint (expr->u.tree.right);
+	    fprintf (stream, " ");
+	    FcExprPrintFile (stream, expr->u.tree.right);
 	    break;
 	case FcOpNot:
-	    printf ("Not ");
-	    FcExprPrint (expr->u.tree.left);
+	    fprintf (stream, "Not ");
+	    FcExprPrintFile (stream, expr->u.tree.left);
 	    break;
 	case FcOpFloor:
-	    printf ("Floor ");
-	    FcExprPrint (expr->u.tree.left);
+	    fprintf (stream, "Floor ");
+	    FcExprPrintFile (stream, expr->u.tree.left);
 	    break;
 	case FcOpCeil:
-	    printf ("Ceil ");
-	    FcExprPrint (expr->u.tree.left);
+	    fprintf (stream, "Ceil ");
+	    FcExprPrintFile (stream, expr->u.tree.left);
 	    break;
 	case FcOpRound:
-	    printf ("Round ");
-	    FcExprPrint (expr->u.tree.left);
+	    fprintf (stream, "Round ");
+	    FcExprPrintFile (stream, expr->u.tree.left);
 	    break;
 	case FcOpTrunc:
-	    printf ("Trunc ");
-	    FcExprPrint (expr->u.tree.left);
+	    fprintf (stream, "Trunc ");
+	    FcExprPrintFile (stream, expr->u.tree.left);
 	    break;
-	case FcOpInvalid: printf ("Invalid"); break;
+	case FcOpInvalid: fprintf (stream, "Invalid"); break;
 	}
 }
 
 void
-FcTestPrint (const FcTest *test)
+FcExprPrint (const FcExpr *expr)
+{
+    FcExprPrintFile (stdout, expr);
+}
+
+void
+FcTestPrintFile (FILE *stream, const FcTest *test)
 {
     switch (test->kind) {
     case FcMatchPattern:
-	printf ("pattern ");
+	fprintf (stream, "pattern ");
 	break;
     case FcMatchFont:
-	printf ("font ");
+	fprintf (stream, "font ");
 	break;
     case FcMatchScan:
-	printf ("scan ");
+	fprintf (stream, "scan ");
 	break;
     case FcMatchKindEnd:
 	/* shouldn't be reached */
@@ -486,36 +544,48 @@ FcTestPrint (const FcTest *test)
     }
     switch (test->qual) {
     case FcQualAny:
-	printf ("any ");
+	fprintf (stream, "any ");
 	break;
     case FcQualAll:
-	printf ("all ");
+	fprintf (stream, "all ");
 	break;
     case FcQualFirst:
-	printf ("first ");
+	fprintf (stream, "first ");
 	break;
     case FcQualNotFirst:
-	printf ("not_first ");
+	fprintf (stream, "not_first ");
 	break;
     }
-    printf ("%s ", FcObjectName (test->object));
-    FcOpPrint (test->op);
-    printf (" ");
-    FcExprPrint (test->expr);
-    printf ("\n");
+    fprintf (stream, "%s ", FcObjectName (test->object));
+    FcOpPrintFile (stream, test->op);
+    fprintf (stream, " ");
+    FcExprPrintFile (stream, test->expr);
+    fprintf (stream, "\n");
+}
+
+void
+FcTestPrint (const FcTest *test)
+{
+    FcTestPrintFile (stdout, test);
+}
+
+void
+FcEditPrintFile (FILE *stream, const FcEdit *edit)
+{
+    fprintf (stream, "Edit %s ", FcObjectName (edit->object));
+    FcOpPrintFile (stream, edit->op);
+    fprintf (stream, " ");
+    FcExprPrintFile (stream, edit->expr);
 }
 
 void
 FcEditPrint (const FcEdit *edit)
 {
-    printf ("Edit %s ", FcObjectName (edit->object));
-    FcOpPrint (edit->op);
-    printf (" ");
-    FcExprPrint (edit->expr);
+    FcEditPrintFile (stdout, edit);
 }
 
 void
-FcRulePrint (const FcRule *rule)
+FcRulePrintFile (FILE *stream, const FcRule *rule)
 {
     FcRuleType    last_type = FcRuleUnknown;
     const FcRule *r;
@@ -524,44 +594,56 @@ FcRulePrint (const FcRule *rule)
 	if (last_type != r->type) {
 	    switch (r->type) {
 	    case FcRuleTest:
-		printf ("[test]\n");
+		fprintf (stream, "[test]\n");
 		break;
 	    case FcRuleEdit:
-		printf ("[edit]\n");
+		fprintf (stream, "[edit]\n");
 		break;
 	    default:
 		break;
 	    }
 	    last_type = r->type;
 	}
-	printf ("\t");
+	fprintf (stream, "\t");
 	switch (r->type) {
 	case FcRuleTest:
-	    FcTestPrint (r->u.test);
+	    FcTestPrintFile (stream, r->u.test);
 	    break;
 	case FcRuleEdit:
-	    FcEditPrint (r->u.edit);
-	    printf (";\n");
+	    FcEditPrintFile (stream, r->u.edit);
+	    fprintf (stream, ";\n");
 	    break;
 	default:
 	    break;
 	}
     }
-    printf ("\n");
+    fprintf (stream, "\n");
 }
 
 void
-FcFontSetPrint (const FcFontSet *s)
+FcRulePrint (const FcRule *rule)
+{
+    FcRulePrintFile (stdout, rule);
+}
+
+void
+FcFontSetPrintFile (FILE *stream, const FcFontSet *s)
 {
     int i;
 
-    printf ("FontSet %d of %d\n", s->nfont, s->sfont);
+    fprintf (stream, "FontSet %d of %d\n", s->nfont, s->sfont);
     for (i = 0; i < s->nfont; i++) {
-	printf ("Font %d ", i);
-	FcPatternPrint (s->fonts[i]);
+	fprintf (stream, "Font %d ", i);
+	FcPatternPrintFile (stream, s->fonts[i]);
     }
 }
 
+void
+FcFontSetPrint (const FcFontSet *s)
+{
+    FcFontSetPrintFile (stdout, s);
+}
+
 int FcDebugVal;
 
 void
@@ -572,7 +654,7 @@ FcInitDebug (void)
 
 	e = getenv ("FC_DEBUG");
 	if (e) {
-	    printf ("FC_DEBUG=%s\n", e);
+	    fprintf (stderr, "FC_DEBUG=%s\n", e);
 	    FcDebugVal = atoi (e);
 	    if (FcDebugVal < 0)
 		FcDebugVal = 0;
diff --git a/src/fcfreetype.c b/src/fcfreetype.c
index aa1c0e2..9a7f1a0 100644
--- a/src/fcfreetype.c
+++ b/src/fcfreetype.c
@@ -2074,6 +2074,37 @@ FcFreeTypeQueryFaceInternal (const FT_Face   face,
 	if (!FcPatternObjectAddString (pat, FC_FONT_WRAPPER_OBJECT, wrapper))
 	    goto bail2;
 
+    {
+	FcPatternElt *elt;
+	FcValueListPtr l;
+	int generic_family = FC_FAMILY_UNKNOWN;
+
+	elt = FcPatternObjectFindElt (pat, FC_FAMILY_OBJECT);
+	for (l = FcPatternEltValues (elt); l; l = FcValueListNext (l)) {
+	    FcValue v = FcValueCanonicalize (&l->value);
+
+	    if (v.type == FcTypeString) {
+		if (FcStrStrIgnoreCase (v.u.s, (FcChar8 *)"mono")) {
+		    generic_family = FC_FAMILY_MONO;
+		    break;
+		} else if (FcStrStrIgnoreCase (v.u.s, (FcChar8 *)"sans")) {
+		    generic_family = FC_FAMILY_SANS;
+		    break;
+		} else if (FcStrStrIgnoreCase (v.u.s, (FcChar8 *)"serif")) {
+		    generic_family = FC_FAMILY_SERIF;
+		    break;
+		} else if (FcStrStrIgnoreCase (v.u.s, (FcChar8 *)"emoji")) {
+		    generic_family = FC_FAMILY_EMOJI;
+		    break;
+		} else if (FcStrStrIgnoreCase (v.u.s, (FcChar8 *)"math")) {
+		    generic_family = FC_FAMILY_MATH;
+		    break;
+		}
+	    }
+	}
+	FcPatternObjectAddInteger(pat, FC_GENERIC_FAMILY_OBJECT, generic_family);
+    }
+
     /*
      * Drop our reference to the charset
      */
diff --git a/src/fcint.h b/src/fcint.h
index 9f03329..6ffd2c2 100644
--- a/src/fcint.h
+++ b/src/fcint.h
@@ -910,41 +910,83 @@ FcReadLink (const FcChar8 *pathname,
 /* fcdbg.c */
 
 FcPrivate void
-FcValuePrintFile (FILE *f, const FcValue v);
+FcValuePrintFile (FILE *stream, const FcValue v);
+
+FcPrivate void
+FcValuePrintFileWithPosition (FILE *stream, const FcValue v, FcBool show_pos_mark);
 
 FcPrivate void
 FcValuePrintWithPosition (const FcValue v, FcBool show_pos_mark);
 
+FcPrivate void
+FcValueListPrintFileWithPosition (FILE *stream, FcValueListPtr l, const FcValueListPtr pos);
+
 FcPrivate void
 FcValueListPrintWithPosition (FcValueListPtr l, const FcValueListPtr pos);
 
+FcPrivate void
+FcValueListPrintFile (FILE *stream, FcValueListPtr l);
+
 FcPrivate void
 FcValueListPrint (FcValueListPtr l);
 
+FcPrivate void
+FcLangSetPrintFile (FILE *stream, const FcLangSet *ls);
+
 FcPrivate void
 FcLangSetPrint (const FcLangSet *ls);
 
+FcPrivate void
+FcOpPrintFile (FILE *stream, FcOp op_);
+
 FcPrivate void
 FcOpPrint (FcOp op);
 
+FcPrivate void
+FcTestPrintFile (FILE *stream, const FcTest *test);
+
 FcPrivate void
 FcTestPrint (const FcTest *test);
 
+FcPrivate void
+FcExprPrintFile (FILE *stream, const FcExpr *expr);
+
 FcPrivate void
 FcExprPrint (const FcExpr *expr);
 
+FcPrivate void
+FcEditPrintFile (FILE *stream, const FcEdit *edit);
+
 FcPrivate void
 FcEditPrint (const FcEdit *edit);
 
+FcPrivate void
+FcRulePrintFile (FILE *stream, const FcRule *rule);
+
 FcPrivate void
 FcRulePrint (const FcRule *rule);
 
+FcPrivate void
+FcCharSetPrintFile (FILE *stream, const FcCharSet *c);
+
 FcPrivate void
 FcCharSetPrint (const FcCharSet *c);
 
+FcPrivate void
+FcPatternPrintFile (FILE *stream, const FcPattern *p);
+
+FcPrivate void
+FcPatternPrint2File (FILE              *stream,
+                     FcPattern         *pp1,
+                     FcPattern         *pp2,
+                     const FcObjectSet *os);
+
 FcPrivate void
 FcPatternPrint2 (FcPattern *p1, FcPattern *p2, const FcObjectSet *os);
 
+FcPrivate void
+FcFontSetPrintFile (FILE *stream, const FcFontSet *s);
+
 extern FcPrivate int FcDebugVal;
 
 #define FcDebug() (FcDebugVal)
diff --git a/src/fcmatch.c b/src/fcmatch.c
index d8523a1..6f71566 100644
--- a/src/fcmatch.c
+++ b/src/fcmatch.c
@@ -328,6 +328,7 @@ typedef enum _FcMatcherPriority {
     PRI1 (COLOR),
     PRI1 (FOUNDRY),
     PRI1 (CHARSET),
+    PRI1 (GENERIC_FAMILY),
     PRI_FAMILY_STRONG,
     PRI_POSTSCRIPT_NAME_STRONG,
     PRI1 (LANG),
diff --git a/src/fcname.c b/src/fcname.c
index fc173d7..4739885 100644
--- a/src/fcname.c
+++ b/src/fcname.c
@@ -203,6 +203,20 @@ static const FcConstant _FcBaseConstants[] = {
     { (FC8) "lcddefault",     "lcdfilter",      FC_LCD_DEFAULT          },
     { (FC8) "lcdlight",       "lcdfilter",      FC_LCD_LIGHT            },
     { (FC8) "lcdlegacy",      "lcdfilter",      FC_LCD_LEGACY           },
+
+    { (FC8) "serif",          "genericfamily",  FC_FAMILY_SERIF         },
+    { (FC8) "sans-serif",     "genericfamily",  FC_FAMILY_SANS          },
+    { (FC8) "monospace",      "genericfamily",  FC_FAMILY_MONO          },
+    { (FC8) "cursive",        "genericfamily",  FC_FAMILY_CURSIVE       },
+    { (FC8) "fantasy",        "genericfamily",  FC_FAMILY_FANTASY       },
+    { (FC8) "system-ui",      "genericfamily",  FC_FAMILY_SYSTEM_UI     },
+    { (FC8) "ui-serif",       "genericfamily",  FC_FAMILY_UI_SERIF      },
+    { (FC8) "ui-sans-serif",  "genericfamily",  FC_FAMILY_UI_SANS       },
+    { (FC8) "ui-monospace",   "genericfamily",  FC_FAMILY_UI_MONO       },
+    { (FC8) "ui-rounded",     "genericfamily",  FC_FAMILY_UI_ROUNDED    },
+    { (FC8) "emoji",          "genericfamily",  FC_FAMILY_EMOJI         },
+    { (FC8) "math",           "genericfamily",  FC_FAMILY_MATH          },
+    { (FC8) "fangsong",       "genericfamily",  FC_FAMILY_FANGSONG      },
 };
 
 #define NUM_FC_CONSTANTS (sizeof _FcBaseConstants / sizeof _FcBaseConstants[0])
diff --git a/src/fcobjs.h b/src/fcobjs.h
index bc17a73..1de0ad0 100644
--- a/src/fcobjs.h
+++ b/src/fcobjs.h
@@ -77,4 +77,5 @@ FC_OBJECT (ORDER,		FcTypeInteger,	FcCompareNumber)
 FC_OBJECT (DESKTOP_NAME,	FcTypeString,	NULL)
 FC_OBJECT (NAMED_INSTANCE,	FcTypeBool,	FcCompareBool)
 FC_OBJECT (FONT_WRAPPER,	FcTypeString,	FcCompareString)
+FC_OBJECT (GENERIC_FAMILY,	FcTypeInteger,	FcCompareNumber)
 /* ^-------------- Add new objects here. */
diff --git a/test/test-48-guessfamily.json b/test/test-48-guessfamily.json
index 63b92d7..7dedbf8 100644
--- a/test/test-48-guessfamily.json
+++ b/test/test-48-guessfamily.json
@@ -1,6 +1,87 @@
 {
-  "fonts": [],
+  "fonts": [
+    {
+      "family": "Liberation Mono",
+      "style": "Regular",
+      "file": "/path/to/LiberationMono-Regular.ttf",
+      "fontversion": 1,
+      "genericfamily": 3
+    },
+    {
+      "family": "Liberation Sans",
+      "style": "Regular",
+      "file": "/path/to/LiberationSans-Regular.ttf",
+      "fontversion": 1,
+      "genericfamily": 2
+    },
+    {
+      "family": "Liberation Serif",
+      "style": "Regular",
+      "file": "/path/to/LiberationSerif-Regular.ttf",
+      "fontversion": 1,
+      "genericfamily": 1
+    },
+    {
+      "family": "Noto Sans Mono",
+      "style": "Regular",
+      "file": "/path/to/NotoSansMono-Regular.ttf",
+      "fontversion": 1,
+      "genericfamily": 3
+    },
+  ],
   "tests": [
+    {
+      "method": "match",
+      "query": {
+        "family": "mono"
+      },
+      "result": {
+        "family": "Liberation Mono",
+        "style": "Regular",
+        "file": "/path/to/LiberationMono-Regular.ttf",
+        "fontversion": 1,
+        "genericfamily": 3
+      }
+    },
+    {
+      "method": "match",
+      "query": {
+        "family": "sans"
+      },
+      "result": {
+        "family": "Liberation Sans",
+        "style": "Regular",
+        "file": "/path/to/LiberationSans-Regular.ttf",
+        "fontversion": 1,
+        "genericfamily": 2
+      }
+    },
+    {
+      "method": "match",
+      "query": {
+        "family": "serif"
+      },
+      "result": {
+        "family": "Liberation Serif",
+        "style": "Regular",
+        "file": "/path/to/LiberationSerif-Regular.ttf",
+        "fontversion": 1,
+        "genericfamily": 1
+      }
+    },
+    {
+      "method": "match",
+      "query": {
+        "genericfamily": "sans-serif"
+      },
+      "result": {
+        "family": "Liberation Sans",
+        "style": "Regular",
+        "file": "/path/to/LiberationSans-Regular.ttf",
+        "fontversion": 1,
+        "genericfamily": 2
+      }
+    },
     {
       "method": "pattern",
       "query": {
@@ -27,6 +108,15 @@
       "result": {
         "family": ["Foo Serif", "serif"]
       }
+    },
+    {
+      "method": "pattern",
+      "query": {
+        "family": "Noto Sans Mono",
+      },
+      "result": {
+        "family": ["Noto Sans Mono", "monospace"]
+      }
     }
   ]
 }
diff --git a/test/test-49-sansserif.json b/test/test-49-sansserif.json
new file mode 100644
index 0000000..80b10ae
--- /dev/null
+++ b/test/test-49-sansserif.json
@@ -0,0 +1,66 @@
+{
+  "fonts": [
+  ],
+  "tests": [
+    {
+      "method": "pattern",
+      "query": {
+        "genericfamily": "sans-serif"
+      },
+      "result": {
+        "genericfamily": "sans-serif",
+        "family": ["sans-serif"]
+      }
+    },
+    {
+      "method": "pattern",
+      "query": {
+        "genericfamily": "monospace"
+      },
+      "result": {
+        "genericfamily": "monospace",
+        "family": ["monospace"]
+      }
+    },
+    {
+      "method": "pattern",
+      "query": {
+        "genericfamily": "serif"
+      },
+      "result": {
+        "genericfamily": "serif",
+        "family": ["serif"]
+      }
+    },
+    {
+      "method": "pattern",
+      "query": {
+        "genericfamily": "emoji"
+      },
+      "result": {
+        "genericfamily": "emoji",
+        "family": ["emoji", "sans-serif"]
+      }
+    },
+    {
+      "method": "pattern",
+      "query": {
+        "genericfamily": "math"
+      },
+      "result": {
+        "genericfamily": "math",
+        "family": ["math", "sans-serif"]
+      }
+    },
+    {
+      "method": "pattern",
+      "query": {
+        "genericfamily": ["monospace", "sans-serif"]
+      },
+      "result": {
+        "genericfamily": ["monospace", "sans-serif"],
+        "family": ["monospace", "sans-serif"]
+      }
+    }
+  ]
+}
diff --git a/test/test-conf.c b/test/test-conf.c
index 1b5aa22..2eef27a 100644
--- a/test/test-conf.c
+++ b/test/test-conf.c
@@ -22,6 +22,10 @@
  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  * PERFORMANCE OF THIS SOFTWARE.
  */
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
 #include <fontconfig/fontconfig.h>
 
 #include <json.h>
@@ -181,28 +185,58 @@ build_pattern (json_object *obj)
 			v.type = FcTypeVoid;
 		    }
 		    continue;
+		} else if (fc_o && fc_o->type == FcTypeInteger) {
+		    for (i = 0; i < n; i++) {
+			o = json_object_array_get_idx (iter.val, i);
+			type = json_object_get_type (o);
+			if (type == json_type_string) {
+			    const FcConstant *c = FcNameGetConstant ((const FcChar8 *)json_object_get_string (o));
+			    if (!c) {
+		                fprintf (stderr, "E: value is not a known constant\n");
+		                fprintf (stderr, "   key: %s\n", iter.key);
+		                fprintf (stderr, "   val: %s (idx: %d)\n", json_object_get_string (iter.val), i);
+		                continue;
+		            }
+		            if (strcmp (c->object, iter.key) != 0) {
+		                fprintf (stderr, "E: value is a constant of different object\n");
+		                fprintf (stderr, "   key: %s\n", iter.key);
+		                fprintf (stderr, "   val: %s (idx: %d)\n", json_object_get_string (iter.val), i);
+		                fprintf (stderr, "   key implied by value: %s\n", c->object);
+		                continue;
+		            }
+			    v.u.i = c->value;
+			} else if (type != json_type_int) {
+			    fprintf (stderr, "E: unable to convert to int\n");
+			    continue;
+			} else {
+			    v.u.i = json_object_get_int(o);
+			}
+			v.type = FcTypeInteger;
+			FcPatternAdd (pat, iter.key, v, FcTrue);
+			v.type = FcTypeVoid;
+		    }
 		} else {
 		    FcLangSet *ls = FcLangSetCreate();
 		    if (!ls) {
-			fprintf (stderr, "E: failed to create langset\n");
-			continue;
+		        fprintf (stderr, "E: failed to create langset\n");
+		        continue;
 		    }
 		    v.type = FcTypeLangSet;
 		    v.u.l = ls;
 		    destroy_v = FcTrue;
 		    for (i = 0; i < n; i++) {
-			o = json_object_array_get_idx (iter.val, i);
-			type = json_object_get_type (o);
-			if (type != json_type_string) {
+		        o = json_object_array_get_idx (iter.val, i);
+		        type = json_object_get_type (o);
+		        if (type != json_type_string) {
 			    fprintf (stderr, "E: langset value not string\n");
 			    FcValueDestroy (v);
 			    continue;
-			}
-			if (FcLangSetAdd (ls, (const FcChar8 *)json_object_get_string (o)) == FcFalse) {
+		        }
+		        if (FcLangSetAdd (ls, (const FcChar8 *)json_object_get_string (o)) == FcFalse) {
 			    fprintf (stderr, "E: failed to add to langset\n");
 			    FcValueDestroy (v);
 			    continue;
-			}
+		        }
 		    }
 		}
 	    } else if (type == json_type_double || type == json_type_int) {
@@ -565,8 +599,8 @@ process_pattern (FcConfig  *config,
                  FcPattern *query,
                  FcPattern *result)
 {
-    FcPatternIter iter;
-    int           x, vc, fail = 0;
+    FcPatternIter  iter1, iter2;
+    int            vc1, vc2, fail = 0, i;
 
     if (!query) {
 	fprintf (stderr, "E: no query defined.\n");
@@ -580,32 +614,41 @@ process_pattern (FcConfig  *config,
     }
     FcConfigSubstitute (config, query, FcMatchPattern);
 
-    FcPatternIterStart (result, &iter);
+    FcPatternIterStart (query, &iter1);
+    FcPatternIterStart (result, &iter2);
     do {
-	vc = FcPatternIterValueCount (result, &iter);
-	for (x = 0; x < vc; x++) {
-	    FcValue vr, vp;
+	const char *obj = FcPatternIterGetObject (result, &iter2);
 
-	    if (FcPatternIterGetValue (result, &iter, x, &vr, NULL) != FcResultMatch) {
-		fprintf (stderr, "E: unable to obtain a value from the expected result\n");
-		fail++;
-		goto bail;
-	    }
-	    if (FcPatternGet (query, FcPatternIterGetObject (result, &iter), x, &vp) != FcResultMatch) {
-		vp.type = FcTypeVoid;
-	    }
-	    if (!FcValueEqual (vp, vr)) {
-		printf ("E: failed to compare %s:\n", FcPatternIterGetObject (result, &iter));
-		printf ("   actual result:");
-		FcValuePrint (vp);
-		printf ("\n   expected result:");
-		FcValuePrint (vr);
-		printf ("\n");
-		fail++;
-		goto bail;
+	if (!FcPatternFindIter (query, &iter1, obj)) {
+	    fprintf (stderr, "E: object (%s) not found in actual result\n", obj);
+	} else {
+	    vc1 = FcPatternIterValueCount (query, &iter1);
+	    vc2 = FcPatternIterValueCount (result, &iter2);
+	    if (vc1 != vc2 || !FcPatternIterEqual (query, &iter1, result, &iter2)) {
+		FcValue v1, v2;
+		FcValueBinding b1, b2;
+
+	        fprintf (stderr, "E: object (%s) mismatched:\n", obj);
+		fprintf (stderr, "   actual result: %d\n    ", vc1);
+		for (i = 0; i < vc1; i++) {
+		    if (FcPatternIterGetValue (query, &iter1, i, &v1, &b1) != FcResultMatch)
+			v1.type = FcTypeVoid;
+		    FcValuePrint (v1);
+		    fprintf (stderr, " ");
+		}
+		fprintf (stderr, "\n");
+	        fprintf (stderr, "   expected result: %d\n    ", vc2);
+		for (i = 0; i < vc1; i++) {
+		    if (FcPatternIterGetValue (result, &iter2, i, &v2, &b2) != FcResultMatch)
+			v2.type = FcTypeVoid;
+		    FcValuePrint (v2);
+		    fprintf (stderr, " ");
+		}
+	        fail++;
+	        goto bail;
 	    }
 	}
-    } while (FcPatternIterNext (result, &iter));
+    } while (FcPatternIterNext (result, &iter2));
  bail:
     return fail;
 }



[Index of Archives]     [Fedora Fonts]     [Fedora Users]     [Fedora Cloud]     [Kernel]     [Fedora Packaging]     [Fedora Desktop]     [PAM]     [Gimp Graphics Editor]     [Yosemite News]

  Powered by Linux