Bug 921858 - distinguish space features in default features from those in non-default features. r=jfkthame

This commit is contained in:
John Daggett 2014-03-20 14:43:30 +08:00
parent 753d90ac34
commit 9209c56b9d
3 changed files with 284 additions and 125 deletions

View File

@ -104,7 +104,6 @@ gfxFontEntry::gfxFontEntry() :
mHasSpaceFeatures(false),
mHasSpaceFeaturesKerning(false),
mHasSpaceFeaturesNonKerning(false),
mHasSpaceFeaturesSubDefault(false),
mCheckedForGraphiteTables(false),
mHasCmapTable(false),
mGrFaceInitialized(false),
@ -116,6 +115,8 @@ gfxFontEntry::gfxFontEntry() :
mGrFace(nullptr),
mGrFaceRefCnt(0)
{
memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
}
gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
@ -131,7 +132,6 @@ gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
mHasSpaceFeatures(false),
mHasSpaceFeaturesKerning(false),
mHasSpaceFeaturesNonKerning(false),
mHasSpaceFeaturesSubDefault(false),
mCheckedForGraphiteTables(false),
mHasCmapTable(false),
mGrFaceInitialized(false),
@ -143,7 +143,8 @@ gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
mGrFace(nullptr),
mGrFaceRefCnt(0)
{
memset(&mHasSpaceFeaturesSub, 0, sizeof(mHasSpaceFeaturesSub));
memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
}
gfxFontEntry::~gfxFontEntry()
@ -1941,36 +1942,6 @@ gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData)
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)
@ -1992,8 +1963,10 @@ CollectLookupsByFeature(hb_face_t *aFace, hb_tag_t aTableTag,
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;
@ -2001,7 +1974,8 @@ CollectLookupsByLanguage(hb_face_t *aFace, hb_tag_t aTableTag,
aScriptIndex,
aLangIndex,
&reqFeatureIndex)) {
CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aLookups);
CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex,
aOtherLookups);
}
uint32_t featureIndexes[32];
@ -2026,31 +2000,101 @@ CollectLookupsByLanguage(hb_face_t *aFace, hb_tag_t aTableTag,
&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
@ -2058,16 +2102,16 @@ HasLookupRuleWithGlyph(hb_face_t *aFace, hb_tag_t aTableTag, bool& aHasGlyph,
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);
@ -2077,25 +2121,75 @@ HasLookupRuleWithGlyph(hb_face_t *aFace, hb_tag_t aTableTag, bool& aHasGlyph,
}
}
// 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()
@ -2104,21 +2198,23 @@ gfxFont::CheckForFeaturesInvolvingSpace()
bool result = false;
hb_face_t *face = GetFontEntry()->GetHBFace();
uint32_t i, len, offset;
uint32_t spaceGlyph = GetSpaceGlyph();
int32_t s;
if (!spaceGlyph) {
return;
}
mFontEntry->mHasSpaceFeaturesSubDefault = false;
hb_face_t *face = GetFontEntry()->GetHBFace();
// 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);
@ -2127,40 +2223,61 @@ gfxFont::CheckForFeaturesInvolvingSpace()
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);
@ -2175,20 +2292,91 @@ gfxFont::CheckForFeaturesInvolvingSpace()
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)
{

View File

@ -438,14 +438,15 @@ public:
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;
@ -1794,53 +1795,20 @@ public:
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;
}
// whether font contains substitution lookups containing spaces
bool HasSubstitutionRulesWithSpaceLookups(int32_t aRunScript);
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 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,
@ -1904,7 +1872,9 @@ protected:
// 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;

View File

@ -501,6 +501,7 @@ gfxPlatform::Shutdown()
delete gGfxPlatformPrefsLock;
gfxPrefs::DestroySingleton();
gfxFont::DestroySingletons();
delete gPlatform;
gPlatform = nullptr;