Bug 921858 - Distinguish space features in default features from those in non-default features. r=jfkthame, a=1.4+
authorJohn Daggett <jdaggett@mozilla.com>
Thu, 20 Mar 2014 14:43:30 +0800
changeset 193090 d140b15c4e1e272876238314ae22af42a9c15c69
parent 193089 f45d255f2e4c585359ef313f99068d752b190eea
child 193091 c963101ad96e12d58dfb64749ccc8b01793f8bb6
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame, 1
bugs921858
milestone30.0a2
Bug 921858 - Distinguish space features in default features from those in non-default features. r=jfkthame, a=1.4+
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxPlatform.cpp
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -103,56 +103,57 @@ gfxFontEntry::gfxFontEntry() :
     mSymbolFont(false),
     mIgnoreGDEF(false),
     mIgnoreGSUB(false),
     mSVGInitialized(false),
     mHasSpaceFeaturesInitialized(false),
     mHasSpaceFeatures(false),
     mHasSpaceFeaturesKerning(false),
     mHasSpaceFeaturesNonKerning(false),
-    mHasSpaceFeaturesSubDefault(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
     mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
     mUVSOffset(0), mUVSData(nullptr),
     mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     mUnitsPerEm(0),
     mHBFace(nullptr),
     mGrFace(nullptr),
     mGrFaceRefCnt(0)
 {
+    memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
+    memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
 }
 
 gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
     mName(aName), mItalic(false), mFixedPitch(false),
     mIsProxy(false), mIsValid(true),
     mIsBadUnderlineFont(false), mIsUserFont(false),
     mIsLocalUserFont(false), mStandardFace(aIsStandardFace),
     mSymbolFont(false),
     mIgnoreGDEF(false),
     mIgnoreGSUB(false),
     mSVGInitialized(false),
     mHasSpaceFeaturesInitialized(false),
     mHasSpaceFeatures(false),
     mHasSpaceFeaturesKerning(false),
     mHasSpaceFeaturesNonKerning(false),
-    mHasSpaceFeaturesSubDefault(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
     mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
     mUVSOffset(0), mUVSData(nullptr),
     mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     mUnitsPerEm(0),
     mHBFace(nullptr),
     mGrFace(nullptr),
     mGrFaceRefCnt(0)
 {
-    memset(&mHasSpaceFeaturesSub, 0, sizeof(mHasSpaceFeaturesSub));
+    memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
+    memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
 }
 
 gfxFontEntry::~gfxFontEntry()
 {
     // For downloaded fonts, we need to tell the user font cache that this
     // entry is being deleted.
     if (!mIsProxy && IsUserFont() && !IsLocalUserFont()) {
         gfxUserFontSet::UserFontCache::ForgetFont(this);
@@ -1950,46 +1951,16 @@ gfxFont::AgeCacheEntry(CacheHashEntry *a
         return PL_DHASH_REMOVE;
     }
     if (aEntry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) {
         return PL_DHASH_REMOVE;
     }
     return PL_DHASH_NEXT;
 }
 
-static bool
-HasLookupRuleWithGlyphByScript(hb_face_t *aFace, hb_tag_t aTableTag,
-                               hb_tag_t aScript, uint16_t aGlyph)
-{
-    hb_set_t *lookups = hb_set_create();
-    hb_set_t *glyphs = hb_set_create();
-    hb_tag_t scripts[2] = {0};
-    scripts[0] = aScript;
-
-    bool result = false;
-    hb_ot_layout_collect_lookups(aFace, aTableTag, scripts, nullptr, nullptr,
-                                 lookups);
-
-    hb_codepoint_t index = -1;
-    while (hb_set_next(lookups, &index)) {
-        hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
-                                           glyphs, glyphs, glyphs,
-                                           glyphs);
-        if (hb_set_has(glyphs, aGlyph)) {
-            result = true;
-            break;
-        }
-    }
-
-    hb_set_destroy(glyphs);
-    hb_set_destroy(lookups);
-
-    return result;
-}
-
 static void
 CollectLookupsByFeature(hb_face_t *aFace, hb_tag_t aTableTag,
                         uint32_t aFeatureIndex, hb_set_t *aLookups)
 {
     uint32_t lookups[32];
     uint32_t i, len, offset;
 
     offset = 0;
@@ -2001,26 +1972,29 @@ CollectLookupsByFeature(hb_face_t *aFace
             hb_set_add(aLookups, lookups[i]);
         }
         offset += len;
     } while (len == ArrayLength(lookups));
 }
 
 static void
 CollectLookupsByLanguage(hb_face_t *aFace, hb_tag_t aTableTag,
-                         hb_tag_t aExcludeFeature,
-                         hb_set_t *aLookups, hb_set_t *aExcludedFeatureLookups,
+                         const nsTHashtable<nsUint32HashKey>&
+                             aSpecificFeatures,
+                         hb_set_t *aOtherLookups,
+                         hb_set_t *aSpecificFeatureLookups,
                          uint32_t aScriptIndex, uint32_t aLangIndex)
 {
     uint32_t reqFeatureIndex;
     if (hb_ot_layout_language_get_required_feature_index(aFace, aTableTag,
                                                          aScriptIndex,
                                                          aLangIndex,
                                                          &reqFeatureIndex)) {
-        CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aLookups);
+        CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex,
+                                aOtherLookups);
     }
 
     uint32_t featureIndexes[32];
     uint32_t i, len, offset;
 
     offset = 0;
     do {
         len = ArrayLength(featureIndexes);
@@ -2035,180 +2009,394 @@ CollectLookupsByLanguage(hb_face_t *aFac
             hb_tag_t featureTag;
             uint32_t tagLen = 1;
             hb_ot_layout_language_get_feature_tags(aFace, aTableTag,
                                                    aScriptIndex, aLangIndex,
                                                    offset + i, &tagLen,
                                                    &featureTag);
 
             // collect lookups
-            hb_set_t *lookups = featureTag == aExcludeFeature ?
-                                    aExcludedFeatureLookups : aLookups;
+            hb_set_t *lookups = aSpecificFeatures.GetEntry(featureTag) ?
+                                    aSpecificFeatureLookups : aOtherLookups;
             CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
         }
         offset += len;
     } while (len == ArrayLength(featureIndexes));
 }
 
+static bool
+HasLookupRuleWithGlyphByScript(hb_face_t *aFace, hb_tag_t aTableTag,
+                               hb_tag_t aScriptTag, uint32_t aScriptIndex,
+                               uint16_t aGlyph,
+                               const nsTHashtable<nsUint32HashKey>&
+                                   aDefaultFeatures,
+                               bool& aHasDefaultFeatureWithGlyph)
+{
+    uint32_t numLangs, lang;
+    hb_set_t *defaultFeatureLookups = hb_set_create();
+    hb_set_t *nonDefaultFeatureLookups = hb_set_create();
+
+    // default lang
+    CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
+                             nonDefaultFeatureLookups, defaultFeatureLookups,
+                             aScriptIndex,
+                             HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
+
+    // iterate over langs
+    numLangs = hb_ot_layout_script_get_language_tags(aFace, aTableTag,
+                                                     aScriptIndex, 0,
+                                                     nullptr, nullptr);
+    for (lang = 0; lang < numLangs; lang++) {
+        CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
+                                 nonDefaultFeatureLookups,
+                                 defaultFeatureLookups,
+                                 aScriptIndex, lang);
+    }
+
+    // look for the glyph among default feature lookups
+    aHasDefaultFeatureWithGlyph = false;
+    hb_set_t *glyphs = hb_set_create();
+    hb_codepoint_t index = -1;
+    while (hb_set_next(defaultFeatureLookups, &index)) {
+        hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
+                                           glyphs, glyphs, glyphs,
+                                           glyphs);
+        if (hb_set_has(glyphs, aGlyph)) {
+            aHasDefaultFeatureWithGlyph = true;
+            break;
+        }
+    }
+
+    // look for the glyph among non-default feature lookups
+    // if no default feature lookups contained spaces
+    bool hasNonDefaultFeatureWithGlyph = false;
+    if (!aHasDefaultFeatureWithGlyph) {
+        hb_set_clear(glyphs);
+        index = -1;
+        while (hb_set_next(nonDefaultFeatureLookups, &index)) {
+            hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
+                                               glyphs, glyphs, glyphs,
+                                               glyphs);
+            if (hb_set_has(glyphs, aGlyph)) {
+                hasNonDefaultFeatureWithGlyph = true;
+                break;
+            }
+        }
+    }
+
+    hb_set_destroy(glyphs);
+    hb_set_destroy(defaultFeatureLookups);
+    hb_set_destroy(nonDefaultFeatureLookups);
+
+    return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph;
+}
+
 static void
 HasLookupRuleWithGlyph(hb_face_t *aFace, hb_tag_t aTableTag, bool& aHasGlyph,
-                       hb_tag_t aExcludeFeature, bool& aHasGlyphExcluded,
+                       hb_tag_t aSpecificFeature, bool& aHasGlyphSpecific,
                        uint16_t aGlyph)
 {
     // iterate over the scripts in the font
     uint32_t numScripts, numLangs, script, lang;
-    hb_set_t *lookups = hb_set_create();
-    hb_set_t *excludedFeatureLookups = hb_set_create();
+    hb_set_t *otherLookups = hb_set_create();
+    hb_set_t *specificFeatureLookups = hb_set_create();
+    nsTHashtable<nsUint32HashKey> specificFeature;
+
+    specificFeature.PutEntry(aSpecificFeature);
 
     numScripts = hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0,
                                                     nullptr, nullptr);
 
     for (script = 0; script < numScripts; script++) {
         // default lang
-        CollectLookupsByLanguage(aFace, aTableTag, aExcludeFeature,
-                                 lookups, excludedFeatureLookups,
+        CollectLookupsByLanguage(aFace, aTableTag, specificFeature,
+                                 otherLookups, specificFeatureLookups,
                                  script, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
 
         // iterate over langs
         numLangs = hb_ot_layout_script_get_language_tags(aFace, HB_OT_TAG_GPOS,
                                                          script, 0,
                                                          nullptr, nullptr);
         for (lang = 0; lang < numLangs; lang++) {
-            CollectLookupsByLanguage(aFace, aTableTag, aExcludeFeature,
-                                     lookups, excludedFeatureLookups,
+            CollectLookupsByLanguage(aFace, aTableTag, specificFeature,
+                                     otherLookups, specificFeatureLookups,
                                      script, lang);
         }
     }
 
-    // look for the glyph among non-excluded lookups
+    // look for the glyph among non-specific feature lookups
     hb_set_t *glyphs = hb_set_create();
     hb_codepoint_t index = -1;
-    while (hb_set_next(lookups, &index)) {
+    while (hb_set_next(otherLookups, &index)) {
         hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
                                            glyphs, glyphs, glyphs,
                                            glyphs);
         if (hb_set_has(glyphs, aGlyph)) {
             aHasGlyph = true;
             break;
         }
     }
 
-    // look for the glyph among excluded lookups
+    // look for the glyph among specific feature lookups
     hb_set_clear(glyphs);
     index = -1;
-    while (hb_set_next(excludedFeatureLookups, &index)) {
+    while (hb_set_next(specificFeatureLookups, &index)) {
         hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
                                            glyphs, glyphs, glyphs,
                                            glyphs);
         if (hb_set_has(glyphs, aGlyph)) {
-            aHasGlyphExcluded = true;
+            aHasGlyphSpecific = true;
             break;
         }
     }
 
     hb_set_destroy(glyphs);
-    hb_set_destroy(excludedFeatureLookups);
-    hb_set_destroy(lookups);
+    hb_set_destroy(specificFeatureLookups);
+    hb_set_destroy(otherLookups);
 }
 
 nsDataHashtable<nsUint32HashKey, int32_t> *gfxFont::sScriptTagToCode = nullptr;
+nsTHashtable<nsUint32HashKey>             *gfxFont::sDefaultFeatures = nullptr;
+
+static inline bool
+HasSubstitution(uint32_t *aBitVector, uint32_t aBit) {
+    return (aBitVector[aBit >> 5] & (1 << (aBit & 0x1f))) != 0;
+}
+
+// union of all default substitution features across scripts
+static const hb_tag_t defaultFeatures[] = {
+    HB_TAG('a','b','v','f'),
+    HB_TAG('a','b','v','s'),
+    HB_TAG('a','k','h','n'),
+    HB_TAG('b','l','w','f'),
+    HB_TAG('b','l','w','s'),
+    HB_TAG('c','a','l','t'),
+    HB_TAG('c','c','m','p'),
+    HB_TAG('c','f','a','r'),
+    HB_TAG('c','j','c','t'),
+    HB_TAG('c','l','i','g'),
+    HB_TAG('f','i','n','2'),
+    HB_TAG('f','i','n','3'),
+    HB_TAG('f','i','n','a'),
+    HB_TAG('h','a','l','f'),
+    HB_TAG('h','a','l','n'),
+    HB_TAG('i','n','i','t'),
+    HB_TAG('i','s','o','l'),
+    HB_TAG('l','i','g','a'),
+    HB_TAG('l','j','m','o'),
+    HB_TAG('l','o','c','l'),
+    HB_TAG('l','t','r','a'),
+    HB_TAG('l','t','r','m'),
+    HB_TAG('m','e','d','2'),
+    HB_TAG('m','e','d','i'),
+    HB_TAG('m','s','e','t'),
+    HB_TAG('n','u','k','t'),
+    HB_TAG('p','r','e','f'),
+    HB_TAG('p','r','e','s'),
+    HB_TAG('p','s','t','f'),
+    HB_TAG('p','s','t','s'),
+    HB_TAG('r','c','l','t'),
+    HB_TAG('r','l','i','g'),
+    HB_TAG('r','k','r','f'),
+    HB_TAG('r','p','h','f'),
+    HB_TAG('r','t','l','a'),
+    HB_TAG('r','t','l','m'),
+    HB_TAG('t','j','m','o'),
+    HB_TAG('v','a','t','u'),
+    HB_TAG('v','e','r','t'),
+    HB_TAG('v','j','m','o')
+};
 
 void
 gfxFont::CheckForFeaturesInvolvingSpace()
 {
     mFontEntry->mHasSpaceFeaturesInitialized = true;
 
     bool result = false;
 
+    uint32_t spaceGlyph = GetSpaceGlyph();
+    if (!spaceGlyph) {
+        return;
+    }
+
     hb_face_t *face = GetFontEntry()->GetHBFace();
 
-    uint32_t i, len, offset;
-    uint32_t spaceGlyph = GetSpaceGlyph();
-    int32_t s;
-
-    mFontEntry->mHasSpaceFeaturesSubDefault = false;
-
     // GSUB lookups - examine per script
     if (hb_ot_layout_has_substitution(face)) {
 
         // set up the script ==> code hashtable if needed
         if (!sScriptTagToCode) {
-            sScriptTagToCode = new nsDataHashtable<nsUint32HashKey, int32_t>(MOZ_NUM_SCRIPT_CODES);
-            for (s = MOZ_SCRIPT_ARABIC; s < MOZ_NUM_SCRIPT_CODES; s++) {
+            sScriptTagToCode =
+                new nsDataHashtable<nsUint32HashKey,
+                                    int32_t>(MOZ_NUM_SCRIPT_CODES);
+            sScriptTagToCode->Put(HB_TAG('D','F','L','T'), MOZ_SCRIPT_COMMON);
+            for (int32_t s = MOZ_SCRIPT_ARABIC; s < MOZ_NUM_SCRIPT_CODES; s++) {
                 hb_script_t scriptTag = hb_script_t(GetScriptTagForCode(s));
                 hb_tag_t s1, s2;
                 hb_ot_tags_from_script(scriptTag, &s1, &s2);
                 sScriptTagToCode->Put(s1, s);
                 if (s2 != HB_OT_TAG_DEFAULT_SCRIPT) {
                     sScriptTagToCode->Put(s2, s);
                 }
             }
+
+            uint32_t numDefaultFeatures = ArrayLength(defaultFeatures);
+            sDefaultFeatures =
+                new nsTHashtable<nsUint32HashKey>(numDefaultFeatures);
+            for (uint32_t i = 0; i < numDefaultFeatures; i++) {
+                sDefaultFeatures->PutEntry(defaultFeatures[i]);
+            }
         }
 
         // iterate over the scripts in the font
         hb_tag_t scriptTags[8];
 
-        offset = 0;
+        uint32_t len, offset = 0;
         do {
             len = ArrayLength(scriptTags);
             hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset,
                                                &len, scriptTags);
-            for (i = 0; i < len; i++) {
-                if (HasLookupRuleWithGlyphByScript(face, HB_OT_TAG_GSUB,
-                                                   scriptTags[i], spaceGlyph))
+            for (uint32_t i = 0; i < len; i++) {
+                bool isDefaultFeature = false;
+                int32_t s;
+                if (!HasLookupRuleWithGlyphByScript(face, HB_OT_TAG_GSUB,
+                                                    scriptTags[i], offset + i,
+                                                    spaceGlyph,
+                                                    *sDefaultFeatures,
+                                                    isDefaultFeature) ||
+                    !sScriptTagToCode->Get(scriptTags[i], &s))
                 {
-                    result = true;
-                    if (scriptTags[i] == HB_TAG('D','F','L','T')) {
-                        mFontEntry->mHasSpaceFeaturesSubDefault = true;
-                    }
-                    if (sScriptTagToCode->Get(scriptTags[i], &s)) {
-                        uint32_t index = s >> 5;
-                        uint32_t bit = s & 0x1f;
-                        mFontEntry->mHasSpaceFeaturesSub[index] |= (1 << bit);
-                    }
+                    continue;
+                }
+                result = true;
+                uint32_t index = s >> 5;
+                uint32_t bit = s & 0x1f;
+                if (isDefaultFeature) {
+                    mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit);
+                } else {
+                    mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit);
                 }
             }
             offset += len;
         } while (len == ArrayLength(scriptTags));
     }
 
+    // spaces in default features of default script?
+    // ==> can't use word cache, skip GPOS analysis
+    bool canUseWordCache = true;
+    if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures,
+                        MOZ_SCRIPT_COMMON)) {
+        canUseWordCache = false;
+    }
+
     // GPOS lookups - distinguish kerning from non-kerning features
     mFontEntry->mHasSpaceFeaturesKerning = false;
     mFontEntry->mHasSpaceFeaturesNonKerning = false;
 
-    if (hb_ot_layout_has_positioning(face)) {
+    if (canUseWordCache && hb_ot_layout_has_positioning(face)) {
         bool hasKerning = false, hasNonKerning = false;
         HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning,
                                HB_TAG('k','e','r','n'), hasKerning, spaceGlyph);
         if (hasKerning || hasNonKerning) {
             result = true;
         }
         mFontEntry->mHasSpaceFeaturesKerning = hasKerning;
         mFontEntry->mHasSpaceFeaturesNonKerning = hasNonKerning;
     }
 
     hb_face_destroy(face);
     mFontEntry->mHasSpaceFeatures = result;
 
 #ifdef DEBUG_SPACE_LOOKUPS
-    printf("font: %s - subst: %8.8x %8.8x %8.8x %8.8x "
-           "default: %s kerning: %s non-kerning: %s\n",
+    printf("font: %s - subst default: %8.8x %8.8x %8.8x %8.8x "
+           "subst non-default: %8.8x %8.8x %8.8x %8.8x "
+           "kerning: %s non-kerning: %s\n",
            NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(),
-           mFontEntry->mHasSpaceFeaturesSub[0],
-           mFontEntry->mHasSpaceFeaturesSub[1],
-           mFontEntry->mHasSpaceFeaturesSub[2],
-           mFontEntry->mHasSpaceFeaturesSub[3],
-           (mFontEntry->mHasSpaceFeaturesSubDefault ? "true" : "false"),
+           mFontEntry->mDefaultSubSpaceFeatures[3],
+           mFontEntry->mDefaultSubSpaceFeatures[2],
+           mFontEntry->mDefaultSubSpaceFeatures[1],
+           mFontEntry->mDefaultSubSpaceFeatures[0],
+           mFontEntry->mNonDefaultSubSpaceFeatures[3],
+           mFontEntry->mNonDefaultSubSpaceFeatures[2],
+           mFontEntry->mNonDefaultSubSpaceFeatures[1],
+           mFontEntry->mNonDefaultSubSpaceFeatures[0],
            (mFontEntry->mHasSpaceFeaturesKerning ? "true" : "false"),
            (mFontEntry->mHasSpaceFeaturesNonKerning ? "true" : "false")
     );
 #endif
 }
 
 bool
+gfxFont::HasSubstitutionRulesWithSpaceLookups(int32_t aRunScript)
+{
+    NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized,
+                 "need to initialize space lookup flags");
+    NS_ASSERTION(aRunScript < MOZ_NUM_SCRIPT_CODES, "weird script code");
+    if (aRunScript == MOZ_SCRIPT_INVALID ||
+        aRunScript >= MOZ_NUM_SCRIPT_CODES) {
+        return false;
+    }
+
+    // default features have space lookups ==> true
+    if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures,
+                        MOZ_SCRIPT_COMMON) ||
+        HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures,
+                        aRunScript))
+    {
+        return true;
+    }
+
+    // non-default features have space lookups and some type of
+    // font feature, in font or style is specified ==> true
+    if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
+                         MOZ_SCRIPT_COMMON) ||
+         HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
+                         aRunScript)) &&
+        (!mStyle.featureSettings.IsEmpty() ||
+         !mFontEntry->mFeatureSettings.IsEmpty()))
+    {
+        return true;
+    }
+
+    return false;
+}
+
+bool
+gfxFont::BypassShapedWordCache(int32_t aRunScript)
+{
+    // We record the presence of space-dependent features in the font entry
+    // so that subsequent instantiations for the same font face won't
+    // require us to re-check the tables; however, the actual check is done
+    // by gfxFont because not all font entry subclasses know how to create
+    // a harfbuzz face for introspection.
+    if (!mFontEntry->mHasSpaceFeaturesInitialized) {
+        CheckForFeaturesInvolvingSpace();
+    }
+
+    if (!mFontEntry->mHasSpaceFeatures) {
+        return false;
+    }
+
+    // if font has substitution rules or non-kerning positioning rules
+    // that involve spaces, bypass
+    if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
+        mFontEntry->mHasSpaceFeaturesNonKerning) {
+        return true;
+    }
+
+    // if kerning explicitly enabled/disabled via font-feature-settings or
+    // font-kerning and kerning rules use spaces, only bypass when enabled
+    if (mKerningSet && mFontEntry->mHasSpaceFeaturesKerning) {
+        return mKerningEnabled;
+    }
+
+    return false;
+}
+
+bool
 gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn)
 {
     aFeatureOn = false;
 
     if (mStyle.featureSettings.IsEmpty() &&
         GetFontEntry()->mFeatureSettings.IsEmpty()) {
         return false;
     }
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -433,24 +433,25 @@ public:
     bool             mSymbolFont  : 1;
     bool             mIgnoreGDEF  : 1;
     bool             mIgnoreGSUB  : 1;
     bool             mSVGInitialized : 1;
     bool             mHasSpaceFeaturesInitialized : 1;
     bool             mHasSpaceFeatures : 1;
     bool             mHasSpaceFeaturesKerning : 1;
     bool             mHasSpaceFeaturesNonKerning : 1;
-    bool             mHasSpaceFeaturesSubDefault : 1;
     bool             mHasGraphiteTables : 1;
     bool             mCheckedForGraphiteTables : 1;
     bool             mHasCmapTable : 1;
     bool             mGrFaceInitialized : 1;
 
-    // bitvector of substitution space features per script
-    uint32_t         mHasSpaceFeaturesSub[(MOZ_NUM_SCRIPT_CODES + 31) / 32];
+    // bitvector of substitution space features per script, one each
+    // for default and non-default features
+    uint32_t         mDefaultSubSpaceFeatures[(MOZ_NUM_SCRIPT_CODES + 31) / 32];
+    uint32_t         mNonDefaultSubSpaceFeatures[(MOZ_NUM_SCRIPT_CODES + 31) / 32];
 
     uint16_t         mWeight;
     int16_t          mStretch;
 
     nsRefPtr<gfxCharacterMap> mCharacterMap;
     uint32_t         mUVSOffset;
     nsAutoArrayPtr<uint8_t> mUVSData;
     nsAutoPtr<gfxUserFontData> mUserFontData;
@@ -1789,63 +1790,30 @@ public:
     friend class GlyphChangeObserver;
 
     bool GlyphsMayChange()
     {
         // Currently only fonts with SVG glyphs can have animated glyphs
         return mFontEntry->TryGetSVGData(this);
     }
 
+    static void DestroySingletons() {
+        delete sScriptTagToCode;
+        delete sDefaultFeatures;
+    }
+
 protected:
     void AddGlyphChangeObserver(GlyphChangeObserver *aObserver);
     void RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver);
 
-    bool HasSubstitutionRulesWithSpaceLookups(int32_t aRunScript) {
-        NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized,
-                     "need to initialize space lookup flags");
-        NS_ASSERTION(aRunScript < MOZ_NUM_SCRIPT_CODES, "weird script code");
-        if (aRunScript == MOZ_SCRIPT_INVALID ||
-            aRunScript >= MOZ_NUM_SCRIPT_CODES) {
-            return false;
-        }
-        uint32_t index = aRunScript >> 5;
-        uint32_t bit = aRunScript & 0x1f;
-        return (mFontEntry->mHasSpaceFeaturesSub[index] & (1 << bit)) != 0;
-    }
-
-    bool BypassShapedWordCache(int32_t aRunScript) {
-        // We record the presence of space-dependent features in the font entry
-        // so that subsequent instantiations for the same font face won't
-        // require us to re-check the tables; however, the actual check is done
-        // by gfxFont because not all font entry subclasses know how to create
-        // a harfbuzz face for introspection.
-        if (!mFontEntry->mHasSpaceFeaturesInitialized) {
-            CheckForFeaturesInvolvingSpace();
-        }
-
-        if (!mFontEntry->mHasSpaceFeatures) {
-            return false;
-        }
-
-        // if font has substitution rules or non-kerning positioning rules
-        // that involve spaces, bypass
-        if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
-            mFontEntry->mHasSpaceFeaturesNonKerning ||
-            mFontEntry->mHasSpaceFeaturesSubDefault) {
-            return true;
-        }
-
-        // if kerning explicitly enabled/disabled via font-feature-settings or
-        // font-kerning and kerning rules use spaces, only bypass when enabled
-        if (mKerningSet && mFontEntry->mHasSpaceFeaturesKerning) {
-            return mKerningEnabled;
-        }
-
-        return false;
-    }
+    // whether font contains substitution lookups containing spaces
+    bool HasSubstitutionRulesWithSpaceLookups(int32_t aRunScript);
+
+    // whether to use word cache or not
+    bool BypassShapedWordCache(int32_t aRunScript);
 
     // For 8-bit text, expand to 16-bit and then call the following method.
     bool ShapeText(gfxContext    *aContext,
                    const uint8_t *aText,
                    uint32_t       aOffset, // dest offset in gfxShapedText
                    uint32_t       aLength,
                    int32_t        aScript,
                    gfxShapedText *aShapedText, // where to store the result
@@ -1899,17 +1867,19 @@ protected:
                                        gfxTextRun *aTextRun);
 
     void CheckForFeaturesInvolvingSpace();
 
     // whether a given feature is included in feature settings from both the
     // font and the style. aFeatureOn set if resolved feature value is non-zero
     bool HasFeatureSet(uint32_t aFeature, bool& aFeatureOn);
 
+    // used when analyzing whether a font has space contextual lookups
     static nsDataHashtable<nsUint32HashKey, int32_t> *sScriptTagToCode;
+    static nsTHashtable<nsUint32HashKey>             *sDefaultFeatures;
 
     nsRefPtr<gfxFontEntry> mFontEntry;
 
     struct CacheHashKey {
         union {
             const uint8_t   *mSingle;
             const char16_t *mDouble;
         }                mText;
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -507,16 +507,17 @@ gfxPlatform::Shutdown()
     // deleted.
     ImageBridgeChild::ShutDown();
 
     CompositorParent::ShutDown();
 
     delete gGfxPlatformPrefsLock;
 
     gfxPrefs::DestroySingleton();
+    gfxFont::DestroySingletons();
 
     delete gPlatform;
     gPlatform = nullptr;
 }
 
 gfxPlatform::~gfxPlatform()
 {
     mScreenReferenceSurface = nullptr;