Bug 1173260 - support multiple families for generics. r=heycam, a=ritu
authorJohn Daggett <jdaggett@mozilla.com>
Wed, 25 Nov 2015 15:30:45 +0900
changeset 291643 14999fc92ea914363d5596650427a418344a9245
parent 291642 fb68430090a5b71a57efe06edf48dbc61e55d6f7
child 291644 8d66f1f8df565865f9cd9f55e8ddc7763b96c918
push id8762
push userjdaggett@mozilla.com
push dateWed, 25 Nov 2015 06:44:11 +0000
treeherdermozilla-aurora@30cb37010709 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam, ritu
bugs1173260
milestone44.0a2
Bug 1173260 - support multiple families for generics. r=heycam, a=ritu
gfx/thebes/gfxFcPlatformFontList.cpp
gfx/thebes/gfxFcPlatformFontList.h
gfx/thebes/gfxPlatformFontList.cpp
gfx/thebes/gfxPlatformFontList.h
--- a/gfx/thebes/gfxFcPlatformFontList.cpp
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -7,24 +7,26 @@
 
 #include "gfxFcPlatformFontList.h"
 #include "gfxFont.h"
 #include "gfxFontConstants.h"
 #include "gfxFontFamilyList.h"
 #include "gfxFT2Utils.h"
 #include "gfxPlatform.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/TimeStamp.h"
 #include "nsGkAtoms.h"
 #include "nsILanguageAtomService.h"
 #include "nsUnicodeProperties.h"
 #include "nsUnicodeRange.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsAppDirectoryServiceDefs.h"
+#include "nsCharSeparatedTokenizer.h"
 
 #include "mozilla/gfx/HelpersCairo.h"
 
 #include <fontconfig/fcfreetype.h>
 
 #ifdef MOZ_WIDGET_GTK
 #include <gdk/gdk.h>
 #endif
@@ -935,16 +937,17 @@ gfxFontconfigFont::GetGlyphRenderingOpti
 }
 #endif
 
 gfxFcPlatformFontList::gfxFcPlatformFontList()
     : mLocalNames(64)
     , mGenericMappings(32)
     , mFcSubstituteCache(64)
     , mLastConfig(nullptr)
+    , mAlwaysUseFontconfigGenerics(true)
 {
     // if the rescan interval is set, start the timer
     int rescanInterval = FcConfigGetRescanInterval(nullptr);
     if (rescanInterval) {
         mLastConfig = FcConfigGetCurrent();
         mCheckFontUpdatesTimer = do_CreateInstance("@mozilla.org/timer;1");
         if (mCheckFontUpdatesTimer) {
             mCheckFontUpdatesTimer->
@@ -1059,22 +1062,22 @@ nsresult
 gfxFcPlatformFontList::InitFontList()
 {
     mLastConfig = FcConfigGetCurrent();
 
     // reset font lists
     gfxPlatformFontList::InitFontList();
 
     mLocalNames.Clear();
-    mGenericMappings.Clear();
     mFcSubstituteCache.Clear();
 
     // iterate over available fonts
     FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem);
     AddFontSetFamilies(systemFonts);
+    mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
 
 #ifdef MOZ_BUNDLED_FONTS
     ActivateBundledFonts();
     FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
     AddFontSetFamilies(appFonts);
 #endif
 
     mOtherFamilyNamesInitialized = true;
@@ -1175,18 +1178,23 @@ gfxFcPlatformFontList::GetFontList(nsIAt
         aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif"));
 }
 
 gfxFontFamily*
 gfxFcPlatformFontList::GetDefaultFont(const gfxFontStyle* aStyle)
 {
     // Get the default font by using a fake name to retrieve the first
     // scalable font that fontconfig suggests for the given language.
-    return FindGenericFamily(NS_LITERAL_STRING("-moz-default"),
-                             aStyle->language);
+    PrefFontList* prefFonts =
+        FindGenericFamilies(NS_LITERAL_STRING("-moz-default"), aStyle->language);
+    NS_ASSERTION(prefFonts, "null list of generic fonts");
+    if (prefFonts && !prefFonts->IsEmpty()) {
+        return (*prefFonts)[0];
+    }
+    return nullptr;
 }
 
 gfxFontEntry*
 gfxFcPlatformFontList::LookupLocalFont(const nsAString& aFontName,
                                        uint16_t aWeight,
                                        int16_t aStretch,
                                        uint8_t aStyle)
 {
@@ -1246,17 +1254,21 @@ gfxFcPlatformFontList::FindFamily(const 
     } else if (familyName.EqualsLiteral("mono")) {
         familyName.AssignLiteral("monospace");
         isDeprecatedGeneric = true;
     }
 
     // fontconfig generics? use fontconfig to determine the family for lang
     if (isDeprecatedGeneric ||
         mozilla::FontFamilyName::Convert(familyName).IsGeneric()) {
-        return FindGenericFamily(familyName, language);
+        PrefFontList* prefFonts = FindGenericFamilies(familyName, language);
+        if (prefFonts && !prefFonts->IsEmpty()) {
+            return (*prefFonts)[0];
+        }
+        return nullptr;
     }
 
     // fontconfig allows conditional substitutions in such a way that it's
     // difficult to distinguish an explicit substitution from other suggested
     // choices. To sniff out explicit substitutions, compare the substitutions
     // for "font, -moz-sentinel" to "-moz-sentinel" to sniff out the
     // substitutions
     //
@@ -1407,16 +1419,84 @@ gfxFcPlatformFontList::GetStandardFamily
             return true;
         }
     }
 
     // didn't find localized name, leave family name blank
     return true;
 }
 
+static const char kFontNamePrefix[] = "font.name.";
+
+void
+gfxFcPlatformFontList::AddGenericFonts(mozilla::FontFamilyType aGenericType,
+                                       nsIAtom* aLanguage,
+                                       nsTArray<gfxFontFamily*>& aFamilyList)
+{
+    bool usePrefFontList = false;
+
+    // treat -moz-fixed as monospace
+    if (aGenericType == eFamily_moz_fixed) {
+        aGenericType = eFamily_monospace;
+    }
+
+    const char* generic = GetGenericName(aGenericType);
+    NS_ASSERTION(generic, "weird generic font type");
+    if (!generic) {
+        return;
+    }
+
+    // By default, most font prefs on Linux map to "use fontconfig"
+    // keywords. So only need to explicitly lookup font pref if
+    // non-default settings exist
+    NS_ConvertASCIItoUTF16 genericToLookup(generic);
+    if ((!mAlwaysUseFontconfigGenerics && aLanguage) ||
+        aLanguage == nsGkAtoms::x_math) {
+        nsIAtom* langGroup = GetLangGroup(aLanguage);
+        nsAutoCString langGroupStr;
+        if (langGroup) {
+            langGroup->ToUTF8String(langGroupStr);
+        }
+        nsAutoCString prefFontName(kFontNamePrefix);
+        prefFontName.Append(generic);
+        prefFontName.Append('.');
+        prefFontName.Append(langGroupStr);
+        nsAdoptingString fontlistValue = Preferences::GetString(prefFontName.get());
+        if (fontlistValue) {
+            if (!fontlistValue.EqualsLiteral("serif") &&
+                !fontlistValue.EqualsLiteral("sans-serif") &&
+                !fontlistValue.EqualsLiteral("monospace")) {
+                usePrefFontList = true;
+            } else {
+                // serif, sans-serif or monospace was specified
+                genericToLookup.Assign(fontlistValue);
+            }
+        }
+    }
+
+    // when pref fonts exist, use standard pref font lookup
+    if (usePrefFontList) {
+        return gfxPlatformFontList::AddGenericFonts(aGenericType,
+                                                    aLanguage,
+                                                    aFamilyList);
+    }
+
+    PrefFontList* prefFonts = FindGenericFamilies(genericToLookup, aLanguage);
+    NS_ASSERTION(prefFonts, "null generic font list");
+    aFamilyList.AppendElements(*prefFonts);
+}
+
+void
+gfxFcPlatformFontList::ClearLangGroupPrefFonts()
+{
+    mGenericMappings.Clear();
+    gfxPlatformFontList::ClearLangGroupPrefFonts();
+    mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+}
+
 /* static */ FT_Library
 gfxFcPlatformFontList::GetFTLibrary()
 {
     if (!sCairoFTLibrary) {
         // Use cairo's FT_Library so that cairo takes care of shutdown of the
         // FT_Library after it has destroyed its font_faces, and FT_Done_Face
         // has been called on each FT_Face, at least until this bug is fixed:
         // https://bugs.freedesktop.org/show_bug.cgi?id=18857
@@ -1447,33 +1527,40 @@ gfxFcPlatformFontList::GetFTLibrary()
         }
 
         sCairoFTLibrary = face.get()->glyph->library;
     }
 
     return sCairoFTLibrary;
 }
 
-gfxFontFamily*
-gfxFcPlatformFontList::FindGenericFamily(const nsAString& aGeneric,
-                                         nsIAtom* aLanguage)
+// a given generic will map to at most this many families
+const uint32_t kMaxGenericFamilies = 3;
+
+gfxPlatformFontList::PrefFontList*
+gfxFcPlatformFontList::FindGenericFamilies(const nsAString& aGeneric,
+                                           nsIAtom* aLanguage)
 {
     // set up name
     NS_ConvertUTF16toUTF8 generic(aGeneric);
 
     nsAutoCString fcLang;
     GetSampleLangForGroup(aLanguage, fcLang);
+    ToLowerCase(fcLang);
 
     nsAutoCString genericLang(generic);
+    if (fcLang.Length() > 0) {
+        genericLang.Append('-');
+    }
     genericLang.Append(fcLang);
 
     // try to get the family from the cache
-    gfxFontFamily *genericFamily = mGenericMappings.GetWeak(genericLang);
-    if (genericFamily) {
-        return genericFamily;
+    PrefFontList* prefFonts = mGenericMappings.Get(genericLang);
+    if (prefFonts) {
+        return prefFonts;
     }
 
     // if not found, ask fontconfig to pick the appropriate font
     nsAutoRef<FcPattern> genericPattern(FcPatternCreate());
     FcPatternAddString(genericPattern, FC_FAMILY,
                        ToFcChar8Ptr(generic.get()));
 
     // -- prefer scalable fonts
@@ -1493,41 +1580,79 @@ gfxFcPlatformFontList::FindGenericFamily
     FcResult result;
     nsAutoRef<FcFontSet> faces(FcFontSort(nullptr, genericPattern, FcFalse,
                                           nullptr, &result));
 
     if (!faces) {
       return nullptr;
     }
 
-    // -- pick the first font for which a font family exists
+    // -- select the fonts to be used for the generic
+    prefFonts = new PrefFontList; // can be empty but in practice won't happen
     for (int i = 0; i < faces->nfont; i++) {
         FcPattern* font = faces->fonts[i];
         FcChar8* mappedGeneric = nullptr;
 
         // not scalable? skip...
         FcBool scalable;
         if (FcPatternGetBool(font, FC_SCALABLE, 0, &scalable) != FcResultMatch ||
             !scalable) {
             continue;
         }
 
         FcPatternGetString(font, FC_FAMILY, 0, &mappedGeneric);
         if (mappedGeneric) {
             NS_ConvertUTF8toUTF16 mappedGenericName(ToCharPtr(mappedGeneric));
-            genericFamily = gfxPlatformFontList::FindFamily(mappedGenericName);
-            if (genericFamily) {
+            gfxFontFamily* genericFamily =
+                gfxPlatformFontList::FindFamily(mappedGenericName);
+            if (genericFamily && !prefFonts->Contains(genericFamily)) {
                 //printf("generic %s ==> %s\n", genericLang.get(), (const char*)mappedGeneric);
-                mGenericMappings.Put(genericLang, genericFamily);
+                prefFonts->AppendElement(genericFamily);
+                if (prefFonts->Length() >= kMaxGenericFamilies) {
+                    break;
+                }
+            }
+        }
+    }
+    mGenericMappings.Put(genericLang, prefFonts);
+    return prefFonts;
+}
+
+bool
+gfxFcPlatformFontList::PrefFontListsUseOnlyGenerics()
+{
+    bool prefFontsUseOnlyGenerics = true;
+    uint32_t count;
+    char** names;
+    nsresult rv = Preferences::GetRootBranch()->
+        GetChildList(kFontNamePrefix, &count, &names);
+    if (NS_SUCCEEDED(rv) && count) {
+        for (size_t i = 0; i < count; i++) {
+            // Check whether all font.name prefs map to generic keywords
+            // and that the pref name and keyword match.
+            //   Ex: font.name.serif.ar ==> "serif" (ok)
+            //   Ex: font.name.serif.ar ==> "monospace" (return false)
+            //   Ex: font.name.serif.ar ==> "DejaVu Serif" (return false)
+
+            nsDependentCString prefName(names[i] +
+                                        ArrayLength(kFontNamePrefix) - 1);
+            nsCCharSeparatedTokenizer tokenizer(prefName, '.');
+            const nsDependentCSubstring& generic = tokenizer.nextToken();
+            const nsDependentCSubstring& langGroup = tokenizer.nextToken();
+            nsAdoptingCString fontPrefValue = Preferences::GetCString(names[i]);
+
+            if (!langGroup.EqualsLiteral("x-math") &&
+                !generic.Equals(fontPrefValue)) {
+                prefFontsUseOnlyGenerics = false;
                 break;
             }
         }
+        NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, names);
     }
-
-    return genericFamily;
+    return prefFontsUseOnlyGenerics;
 }
 
 /* static */ void
 gfxFcPlatformFontList::CheckFontUpdates(nsITimer *aTimer, void *aThis)
 {
     // check for font updates
     FcInitBringUptoDate();
 
--- a/gfx/thebes/gfxFcPlatformFontList.h
+++ b/gfx/thebes/gfxFcPlatformFontList.h
@@ -221,48 +221,67 @@ public:
     gfxFontFamily* FindFamily(const nsAString& aFamily,
                               gfxFontStyle* aStyle = nullptr) override;
 
     bool GetStandardFamilyName(const nsAString& aFontName,
                                nsAString& aFamilyName) override;
 
     FcConfig* GetLastConfig() const { return mLastConfig; }
 
+    // override to use fontconfig lookup for generics
+    void AddGenericFonts(mozilla::FontFamilyType aGenericType,
+                         nsIAtom* aLanguage,
+                         nsTArray<gfxFontFamily*>& aFamilyList) override;
+
+    void ClearLangGroupPrefFonts() override;
+
     static FT_Library GetFTLibrary();
 
 protected:
     virtual ~gfxFcPlatformFontList();
 
     // add all the font families found in a font set
     void AddFontSetFamilies(FcFontSet* aFontSet);
 
-    // figure out which family fontconfig maps a generic to
+    // figure out which families fontconfig maps a generic to
     // (aGeneric assumed already lowercase)
-    gfxFontFamily* FindGenericFamily(const nsAString& aGeneric,
-                                     nsIAtom* aLanguage);
+    PrefFontList* FindGenericFamilies(const nsAString& aGeneric,
+                                      nsIAtom* aLanguage);
+
+    // are all pref font settings set to use fontconfig generics?
+    bool PrefFontListsUseOnlyGenerics();
 
     static void CheckFontUpdates(nsITimer *aTimer, void *aThis);
 
 #ifdef MOZ_BUNDLED_FONTS
     void ActivateBundledFonts();
     nsCString mBundledFontsPath;
     bool mBundledFontsInitialized;
 #endif
 
     // to avoid enumerating all fonts, maintain a mapping of local font
     // names to family
     nsBaseHashtable<nsStringHashKey,
                     nsCountedRef<FcPattern>,
                     FcPattern*> mLocalNames;
 
-    // caching generic/lang ==> font family
-    nsRefPtrHashtable<nsCStringHashKey, gfxFontFamily> mGenericMappings;
+    // caching generic/lang ==> font family list
+    nsBaseHashtable<nsCStringHashKey,
+                    nsAutoPtr<PrefFontList>,
+                    PrefFontList*> mGenericMappings;
 
     // caching family lookups as found by FindFamily after resolving substitutions
     nsRefPtrHashtable<nsCStringHashKey, gfxFontFamily> mFcSubstituteCache;
 
     nsCOMPtr<nsITimer> mCheckFontUpdatesTimer;
     nsCountedRef<FcConfig> mLastConfig;
 
+    // By default, font prefs under Linux are set to simply lookup
+    // via fontconfig the appropriate font for serif/sans-serif/monospace.
+    // Rather than check each time a font pref is used, check them all at startup
+    // and set a boolean to flag the case that non-default user font prefs exist
+    // Note: langGroup == x-math is handled separately
+    bool mAlwaysUseFontconfigGenerics;
+
     static FT_Library sCairoFTLibrary;
 };
 
 #endif /* GFXPLATFORMFONTLIST_H_ */
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -799,49 +799,17 @@ gfxPlatformFontList::RemoveCmap(const gf
 void
 gfxPlatformFontList::ResolveGenericFontNames(
     FontFamilyType aGenericType,
     eFontPrefLang aPrefLang,
     nsTArray<RefPtr<gfxFontFamily>>* aGenericFamilies
 )
 {
     const char* langGroupStr = GetPrefLangName(aPrefLang);
-
-    static const char kGeneric_serif[] = "serif";
-    static const char kGeneric_sans_serif[] = "sans-serif";
-    static const char kGeneric_monospace[] = "monospace";
-    static const char kGeneric_cursive[] = "cursive";
-    static const char kGeneric_fantasy[] = "fantasy";
-
-    // type should be standard generic type at this point
-    NS_ASSERTION(aGenericType >= eFamily_serif &&
-                 aGenericType <= eFamily_fantasy,
-                 "standard generic font family type required");
-
-    // map generic type to string
-    const char *generic = nullptr;
-    switch (aGenericType) {
-        case eFamily_serif:
-            generic = kGeneric_serif;
-            break;
-        case eFamily_sans_serif:
-            generic = kGeneric_sans_serif;
-            break;
-        case eFamily_monospace:
-            generic = kGeneric_monospace;
-            break;
-        case eFamily_cursive:
-            generic = kGeneric_cursive;
-            break;
-        case eFamily_fantasy:
-            generic = kGeneric_fantasy;
-            break;
-        default:
-            break;
-    }
+    const char* generic = GetGenericName(aGenericType);
 
     if (!generic) {
         return;
     }
 
     nsAutoTArray<nsString,4> genericFamilies;
 
     // load family for "font.name.generic.lang"
@@ -911,29 +879,17 @@ gfxPlatformFontList::GetPrefFontsLangGro
 }
 
 void
 gfxPlatformFontList::AddGenericFonts(mozilla::FontFamilyType aGenericType,
                                      nsIAtom* aLanguage,
                                      nsTArray<gfxFontFamily*>& aFamilyList)
 {
     // map lang ==> langGroup
-    nsIAtom *langGroup = nullptr;
-    if (aLanguage) {
-        if (!mLangService) {
-            mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID);
-        }
-        if (mLangService) {
-            nsresult rv;
-            langGroup = mLangService->GetLanguageGroup(aLanguage, &rv);
-        }
-    }
-    if (!langGroup) {
-        langGroup = nsGkAtoms::Unicode;
-    }
+    nsIAtom* langGroup = GetLangGroup(aLanguage);
 
     // langGroup ==> prefLang
     eFontPrefLang prefLang = GetFontPrefLangFor(langGroup);
 
     // lookup pref fonts
     nsTArray<RefPtr<gfxFontFamily>>* prefFonts =
         GetPrefFontsLangGroup(aGenericType, prefLang);
 
@@ -1216,16 +1172,75 @@ void
 gfxPlatformFontList::GetFontFamilyNames(nsTArray<nsString>& aFontFamilyNames)
 {
     for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
         RefPtr<gfxFontFamily>& family = iter.Data();
         aFontFamilyNames.AppendElement(family->Name());
     }
 }
 
+nsIAtom*
+gfxPlatformFontList::GetLangGroup(nsIAtom* aLanguage)
+{
+    // map lang ==> langGroup
+    nsIAtom *langGroup = nullptr;
+    if (aLanguage) {
+        if (!mLangService) {
+            mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID);
+        }
+        if (mLangService) {
+            nsresult rv;
+            langGroup = mLangService->GetLanguageGroup(aLanguage, &rv);
+        }
+    }
+    if (!langGroup) {
+        langGroup = nsGkAtoms::Unicode;
+    }
+    return langGroup;
+}
+
+/* static */ const char*
+gfxPlatformFontList::GetGenericName(FontFamilyType aGenericType)
+{
+    static const char kGeneric_serif[] = "serif";
+    static const char kGeneric_sans_serif[] = "sans-serif";
+    static const char kGeneric_monospace[] = "monospace";
+    static const char kGeneric_cursive[] = "cursive";
+    static const char kGeneric_fantasy[] = "fantasy";
+
+    // type should be standard generic type at this point
+    NS_ASSERTION(aGenericType >= eFamily_serif &&
+                 aGenericType <= eFamily_fantasy,
+                 "standard generic font family type required");
+
+    // map generic type to string
+    const char *generic = nullptr;
+    switch (aGenericType) {
+        case eFamily_serif:
+            generic = kGeneric_serif;
+            break;
+        case eFamily_sans_serif:
+            generic = kGeneric_sans_serif;
+            break;
+        case eFamily_monospace:
+            generic = kGeneric_monospace;
+            break;
+        case eFamily_cursive:
+            generic = kGeneric_cursive;
+            break;
+        case eFamily_fantasy:
+            generic = kGeneric_fantasy;
+            break;
+        default:
+            break;
+    }
+
+    return generic;
+}
+
 void
 gfxPlatformFontList::InitLoader()
 {
     GetFontFamilyNames(mFontInfo->mFontFamiliesToLoad);
     mStartIndex = 0;
     mNumFamilies = mFontInfo->mFontFamiliesToLoad.Length();
     memset(&(mFontInfo->mLoadStats), 0, sizeof(mFontInfo->mLoadStats));
 }
--- a/gfx/thebes/gfxPlatformFontList.h
+++ b/gfx/thebes/gfxPlatformFontList.h
@@ -115,17 +115,17 @@ public:
     virtual nsresult InitFontList();
 
     virtual void GetFontList(nsIAtom *aLangGroup,
                              const nsACString& aGenericFamily,
                              nsTArray<nsString>& aListOfFonts);
 
     void UpdateFontList();
 
-    void ClearLangGroupPrefFonts();
+    virtual void ClearLangGroupPrefFonts();
 
     virtual void GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily> >& aFamilyArray);
 
     gfxFontEntry*
     SystemFindFontForChar(uint32_t aCh, uint32_t aNextCh,
                           int32_t aRunScript,
                           const gfxFontStyle* aStyle);
 
@@ -309,16 +309,21 @@ protected:
 
     static PLDHashOperator
         HashEnumFuncForFamilies(nsStringHashKey::KeyType aKey,
                                 RefPtr<gfxFontFamily>& aFamilyEntry,
                                 void* aUserArg);
 
     virtual void GetFontFamilyNames(nsTArray<nsString>& aFontFamilyNames);
 
+    // helper function to map lang to lang group
+    nsIAtom* GetLangGroup(nsIAtom* aLanguage);
+
+    static const char* GetGenericName(mozilla::FontFamilyType aGenericType);
+
     // gfxFontInfoLoader overrides, used to load in font cmaps
     virtual void InitLoader();
     virtual bool LoadFontInfo();
     virtual void CleanupLoader();
 
     // read the loader initialization prefs, and start it
     void GetPrefsAndStartLoader();