Bug 1173260 - support multiple families for generics. r=heycam
authorJohn Daggett <jdaggett@mozilla.com>
Mon, 16 Nov 2015 08:48:40 +0900
changeset 272734 f77f6580b31c1f007a8fc026e736a1f87d020259
parent 272733 95ae26515feb7c931d5acc5d721562f66d855e5e
child 272735 236908ce084c6f22379eb32e7635737b4e02d8fd
push id29682
push userkwierso@gmail.com
push dateTue, 17 Nov 2015 01:21:10 +0000
treeherdermozilla-central@a2f83cbe53ac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1173260
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1173260 - support multiple families for generics. r=heycam
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
@@ -781,49 +781,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"
@@ -893,29 +861,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);
 
@@ -1198,16 +1154,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();