Bug 549861. Implement gfx support for font-variant-alternates. r=jkew
authorJohn Daggett <jdaggett@mozilla.com>
Mon, 13 May 2013 18:45:37 +0900
changeset 138875 d97d070db34414267f1fbf5379eb896d4ff47c0d
parent 138874 dea9df4a487db3ccafb3332946a2480aeb22f5a5
child 138876 931349bb42e3dfd71b5cff70de01a08c6c28d8b9
push idunknown
push userunknown
push dateunknown
reviewersjkew
bugs549861
milestone23.0a1
Bug 549861. Implement gfx support for font-variant-alternates. r=jkew
gfx/src/nsFont.cpp
gfx/src/nsFont.h
gfx/thebes/Makefile.in
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxFontFeatures.cpp
gfx/thebes/gfxFontFeatures.h
gfx/thebes/gfxGraphiteShaper.cpp
gfx/thebes/gfxHarfBuzzShaper.cpp
--- a/gfx/src/nsFont.cpp
+++ b/gfx/src/nsFont.cpp
@@ -75,16 +75,18 @@ nsFont::nsFont(const nsFont& aOther)
   fontFeatureSettings = aOther.fontFeatureSettings;
   languageOverride = aOther.languageOverride;
   variantAlternates = aOther.variantAlternates;
   variantCaps = aOther.variantCaps;
   variantEastAsian = aOther.variantEastAsian;
   variantLigatures = aOther.variantLigatures;
   variantNumeric = aOther.variantNumeric;
   variantPosition = aOther.variantPosition;
+  alternateValues = aOther.alternateValues;
+  featureValueLookup = aOther.featureValueLookup;
 }
 
 nsFont::nsFont()
 {
 }
 
 nsFont::~nsFont()
 {
@@ -103,17 +105,19 @@ bool nsFont::BaseEquals(const nsFont& aO
       (synthesis == aOther.synthesis) &&
       (fontFeatureSettings == aOther.fontFeatureSettings) &&
       (languageOverride == aOther.languageOverride) &&
       (variantAlternates == aOther.variantAlternates) &&
       (variantCaps == aOther.variantCaps) &&
       (variantEastAsian == aOther.variantEastAsian) &&
       (variantLigatures == aOther.variantLigatures) &&
       (variantNumeric == aOther.variantNumeric) &&
-      (variantPosition == aOther.variantPosition)) {
+      (variantPosition == aOther.variantPosition) &&
+      (alternateValues == aOther.alternateValues) &&
+      (featureValueLookup == aOther.featureValueLookup)) {
     return true;
   }
   return false;
 }
 
 bool nsFont::Equals(const nsFont& aOther) const
 {
   if (BaseEquals(aOther) &&
@@ -140,19 +144,29 @@ nsFont& nsFont::operator=(const nsFont& 
   fontFeatureSettings = aOther.fontFeatureSettings;
   languageOverride = aOther.languageOverride;
   variantAlternates = aOther.variantAlternates;
   variantCaps = aOther.variantCaps;
   variantEastAsian = aOther.variantEastAsian;
   variantLigatures = aOther.variantLigatures;
   variantNumeric = aOther.variantNumeric;
   variantPosition = aOther.variantPosition;
+  alternateValues = aOther.alternateValues;
+  featureValueLookup = aOther.featureValueLookup;
   return *this;
 }
 
+void
+nsFont::CopyAlternates(const nsFont& aOther)
+{
+  variantAlternates = aOther.variantAlternates;
+  alternateValues = aOther.alternateValues;
+  featureValueLookup = aOther.featureValueLookup;
+}
+
 static bool IsGenericFontFamily(const nsString& aFamily)
 {
   uint8_t generic;
   nsFont::GetGenericID(aFamily, &generic);
   return generic != kGenericFont_NONE;
 }
 
 const PRUnichar kNullCh       = PRUnichar('\0');
@@ -296,16 +310,29 @@ void nsFont::AddFontFeaturesToStyle(gfxF
       aStyle->kerning = true;
       aStyle->featureSettings.AppendElement(setting);
       break;
     default:
       // auto case implies use user agent default
       break;
   }
 
+  // -- alternates
+  if (variantAlternates & NS_FONT_VARIANT_ALTERNATES_HISTORICAL) {
+    setting.mValue = 1;
+    setting.mTag = TRUETYPE_TAG('h','i','s','t');
+    aStyle->featureSettings.AppendElement(setting);
+  }
+
+
+  // -- copy font-specific alternate info into style
+  //    (this will be resolved after font-matching occurs)
+  aStyle->alternateValues.AppendElements(alternateValues);
+  aStyle->featureValueLookup = featureValueLookup;
+
   // -- caps
   setting.mValue = 1;
   switch (variantCaps) {
     case NS_FONT_VARIANT_CAPS_ALLSMALL:
       setting.mTag = TRUETYPE_TAG('c','2','s','c');
       aStyle->featureSettings.AppendElement(setting);
       // fall through to the small-caps case
     case NS_FONT_VARIANT_CAPS_SMALLCAPS:
--- a/gfx/src/nsFont.h
+++ b/gfx/src/nsFont.h
@@ -7,16 +7,17 @@
 #define nsFont_h___
 
 #include "gfxCore.h"
 #include "nsCoord.h"
 #include "nsStringGlue.h"
 #include "nsTArray.h"
 #include "gfxFontConstants.h"
 #include "gfxFontFeatures.h"
+#include "nsAutoPtr.h"
 
 // XXX we need a method to enumerate all of the possible fonts on the
 // system across family, weight, style, size, etc. But not here!
 
 // Enumerator callback function. Return false to stop
 typedef bool (*nsFontFamilyEnumFunc)(const nsString& aFamily, bool aGeneric, void *aData);
 
 // IDs for generic fonts
@@ -81,16 +82,22 @@ struct NS_GFX nsFont {
   nscoord size;
 
   // The aspect-value (ie., the ratio actualsize:actualxheight) that any
   // actual physical font created from this font structure must have when
   // rendering or measuring a string. A value of 0 means no adjustment
   // needs to be done.
   float sizeAdjust;
 
+  // -- list of value tags for font-specific alternate features
+  nsTArray<gfxAlternateValue> alternateValues;
+
+  // -- object used to look these up once the font is matched
+  nsRefPtr<gfxFontFeatureValueSet> featureValueLookup;
+
   // Font features from CSS font-feature-settings
   nsTArray<gfxFontFeature> fontFeatureSettings;
 
   // Language system tag, to override document language;
   // this is an OpenType "language system" tag represented as a 32-bit integer
   // (see http://www.microsoft.com/typography/otspec/languagetags.htm).
   nsString languageOverride;
 
@@ -121,16 +128,18 @@ struct NS_GFX nsFont {
   }
 
   bool Equals(const nsFont& aOther) const ;
   // Compare ignoring differences in 'variant' and 'decoration'
   bool BaseEquals(const nsFont& aOther) const;
 
   nsFont& operator=(const nsFont& aOther);
 
+  void CopyAlternates(const nsFont& aOther);
+
   // Add featureSettings into style
   void AddFontFeaturesToStyle(gfxFontStyle *aStyle) const;
 
   // Utility method to interpret name string
   // enumerates all families specified by this font only
   // returns true if completed, false if stopped
   // enclosing quotes will be removed, and whitespace compressed (as needed)
   bool EnumerateFamilies(nsFontFamilyEnumFunc aFunc, void* aData) const;
--- a/gfx/thebes/Makefile.in
+++ b/gfx/thebes/Makefile.in
@@ -28,16 +28,17 @@ CPPSRCS	= \
 	gfxASurface.cpp \
 	gfxAlphaRecovery.cpp \
 	gfxBlur.cpp \
 	gfxCachedTempSurface.cpp \
 	gfxContext.cpp \
 	gfxDrawable.cpp \
 	gfxImageSurface.cpp \
 	gfxFont.cpp \
+	gfxFontFeatures.cpp \
 	gfxFontMissingGlyphs.cpp \
 	gfxFontTest.cpp \
 	gfxFontUtils.cpp \
 	gfxMatrix.cpp \
 	gfxPath.cpp \
 	gfxPattern.cpp \
 	gfxPlatform.cpp \
 	gfxPlatformFontList.cpp \
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1367,30 +1367,121 @@ gfxFontCache::SizeOfExcludingThis(nsMall
 void
 gfxFontCache::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf,
                                   FontCacheSizes*   aSizes) const
 {
     aSizes->mFontInstances += aMallocSizeOf(this);
     SizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
+#define MAX_SSXX_VALUE 99
+#define MAX_CVXX_VALUE 99
+
+static void
+LookupAlternateValues(gfxFontFeatureValueSet *featureLookup,
+                      const nsAString& aFamily,
+                      const nsTArray<gfxAlternateValue>& altValue,
+                      nsTArray<gfxFontFeature>& aFontFeatures)
+{
+    uint32_t numAlternates = altValue.Length();
+    for (uint32_t i = 0; i < numAlternates; i++) {
+        const gfxAlternateValue& av = altValue.ElementAt(i);
+        nsAutoTArray<uint32_t,4> values;
+
+        // map <family, name, feature> ==> <values>
+        bool found =
+            featureLookup->GetFontFeatureValuesFor(aFamily, av.alternate,
+                                                   av.value, values);
+        uint32_t numValues = values.Length();
+
+        // nothing defined, skip
+        if (!found || numValues == 0) {
+            continue;
+        }
+
+        gfxFontFeature feature;
+        if (av.alternate == NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT) {
+            NS_ASSERTION(numValues <= 2,
+                         "too many values allowed for character-variant");
+            // character-variant(12 3) ==> 'cv12' = 3
+            uint32_t nn = values.ElementAt(0);
+            // ignore values greater than 99
+            if (nn == 0 || nn > MAX_CVXX_VALUE) {
+                continue;
+            }
+            feature.mValue = 1;
+            if (numValues > 1) {
+                feature.mValue = values.ElementAt(1);
+            }
+            feature.mTag = HB_TAG('c','v',('0' + nn / 10), ('0' + nn % 10));
+            aFontFeatures.AppendElement(feature);
+
+        } else if (av.alternate == NS_FONT_VARIANT_ALTERNATES_STYLESET) {
+            // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1
+            feature.mValue = 1;
+            for (uint32_t v = 0; v < numValues; v++) {
+                uint32_t nn = values.ElementAt(v);
+                if (nn == 0 || nn > MAX_SSXX_VALUE) {
+                    continue;
+                }
+                feature.mTag = HB_TAG('s','s',('0' + nn / 10), ('0' + nn % 10));
+                aFontFeatures.AppendElement(feature);
+            }
+
+        } else {
+            NS_ASSERTION(numValues == 1,
+                   "too many values for font-specific font-variant-alternates");
+            feature.mValue = values.ElementAt(0);
+
+            switch (av.alternate) {
+                case NS_FONT_VARIANT_ALTERNATES_STYLISTIC:  // salt
+                    feature.mTag = HB_TAG('s','a','l','t');
+                    break;
+                case NS_FONT_VARIANT_ALTERNATES_SWASH:  // swsh, cswh
+                    feature.mTag = HB_TAG('s','w','s','h');
+                    aFontFeatures.AppendElement(feature);
+                    feature.mTag = HB_TAG('c','s','w','h');
+                    break;
+                case NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: // ornm
+                    feature.mTag = HB_TAG('o','r','n','m');
+                    break;
+                case NS_FONT_VARIANT_ALTERNATES_ANNOTATION: // nalt
+                    feature.mTag = HB_TAG('n','a','l','t');
+                    break;
+                default:
+                    feature.mTag = 0;
+                    break;
+            }
+
+            NS_ASSERTION(feature.mTag, "unsupported alternate type");
+            if (!feature.mTag) {
+                continue;
+            }
+            aFontFeatures.AppendElement(feature);
+        }
+    }
+}
+
 /* static */ bool
 gfxFontShaper::MergeFontFeatures(
     const gfxFontStyle *aStyle,
     const nsTArray<gfxFontFeature>& aFontFeatures,
     bool aDisableLigatures,
+    const nsAString& aFamilyName,
     nsDataHashtable<nsUint32HashKey,uint32_t>& aMergedFeatures)
 {
+    uint32_t numAlts = aStyle->alternateValues.Length();
     const nsTArray<gfxFontFeature>& styleRuleFeatures =
         aStyle->featureSettings;
 
     // bail immediately if nothing to do
     if (styleRuleFeatures.IsEmpty() &&
         aFontFeatures.IsEmpty() &&
-        !aDisableLigatures) {
+        !aDisableLigatures &&
+        numAlts == 0) {
         return false;
     }
 
     aMergedFeatures.Init();
 
     // Ligature features are enabled by default in the generic shaper,
     // so we explicitly turn them off if necessary (for letter-spacing)
     if (aDisableLigatures) {
@@ -1402,16 +1493,31 @@ gfxFontShaper::MergeFontFeatures(
     uint32_t i, count;
 
     count = aFontFeatures.Length();
     for (i = 0; i < count; i++) {
         const gfxFontFeature& feature = aFontFeatures.ElementAt(i);
         aMergedFeatures.Put(feature.mTag, feature.mValue);
     }
 
+    // add font-specific feature values from style rules
+    if (aStyle->featureValueLookup && numAlts > 0) {
+        nsAutoTArray<gfxFontFeature,4> featureList;
+
+        // insert list of alternate feature settings
+        LookupAlternateValues(aStyle->featureValueLookup, aFamilyName,
+                              aStyle->alternateValues, featureList);
+
+        count = featureList.Length();
+        for (i = 0; i < count; i++) {
+            const gfxFontFeature& feature = featureList.ElementAt(i);
+            aMergedFeatures.Put(feature.mTag, feature.mValue);
+        }
+    }
+
     // add feature values from style rules
     count = styleRuleFeatures.Length();
     for (i = 0; i < count; i++) {
         const gfxFontFeature& feature = styleRuleFeatures.ElementAt(i);
         aMergedFeatures.Put(feature.mTag, feature.mValue);
     }
 
     return aMergedFeatures.Count() != 0;
@@ -4794,23 +4900,25 @@ gfxFontStyle::gfxFontStyle(uint8_t aStyl
     if (!language) {
         NS_WARNING("null language");
         language = nsGkAtoms::x_western;
     }
 }
 
 gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) :
     language(aStyle.language),
+    featureValueLookup(aStyle.featureValueLookup),
     size(aStyle.size), sizeAdjust(aStyle.sizeAdjust),
     languageOverride(aStyle.languageOverride),
     weight(aStyle.weight), stretch(aStyle.stretch),
     systemFont(aStyle.systemFont), printerFont(aStyle.printerFont),
     kerning(aStyle.kerning), style(aStyle.style)
 {
     featureSettings.AppendElements(aStyle.featureSettings);
+    alternateValues.AppendElements(aStyle.alternateValues);
 }
 
 int8_t
 gfxFontStyle::ComputeWeight() const
 {
     int8_t baseWeight = (weight + 50) / 100;
 
     if (baseWeight < 0)
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -75,16 +75,26 @@ struct THEBES_API gfxFontStyle {
 
     // Features are composed of (1) features from style rules (2) features
     // from feature setttings rules and (3) family-specific features.  (1) and
     // (3) are guaranteed to be mutually exclusive
 
     // custom opentype feature settings
     nsTArray<gfxFontFeature> featureSettings;
 
+    // Some font-variant property values require font-specific settings
+    // defined via @font-feature-values rules.  These are resolved after
+    // font matching occurs.
+
+    // -- list of value tags for specific alternate features
+    nsTArray<gfxAlternateValue> alternateValues;
+
+    // -- object used to look these up once the font is matched
+    nsRefPtr<gfxFontFeatureValueSet> featureValueLookup;
+
     // The logical size of the font, in pixels
     gfxFloat size;
 
     // The aspect-value (ie., the ratio actualsize:actualxheight) that any
     // actual physical font created from this font structure must have when
     // rendering or measuring a string. A value of 0 means no adjustment
     // needs to be done.
     float sizeAdjust;
@@ -147,17 +157,19 @@ struct THEBES_API gfxFontStyle {
             (printerFont == other.printerFont) &&
             (weight == other.weight) &&
             (stretch == other.stretch) &&
             (language == other.language) &&
             (*reinterpret_cast<const uint32_t*>(&sizeAdjust) ==
              *reinterpret_cast<const uint32_t*>(&other.sizeAdjust)) &&
             (kerning == other.kerning) &&
             (featureSettings == other.featureSettings) &&
-            (languageOverride == other.languageOverride);
+            (languageOverride == other.languageOverride) &&
+            (alternateValues == other.alternateValues) &&
+            (featureValueLookup == other.featureValueLookup);
     }
 
     static void ParseFontFeatureSettings(const nsString& aFeatureString,
                                          nsTArray<gfxFontFeature>& aFeatures);
 
     static uint32_t ParseFontLanguageOverride(const nsString& aLangTag);
 };
 
@@ -1153,16 +1165,17 @@ public:
 
     gfxFont *GetFont() const { return mFont; }
 
     // returns true if features exist in output, false otherwise
     static bool
     MergeFontFeatures(const gfxFontStyle *aStyle,
                       const nsTArray<gfxFontFeature>& aFontFeatures,
                       bool aDisableLigatures,
+                      const nsAString& aFamilyName,
                       nsDataHashtable<nsUint32HashKey,uint32_t>& aMergedFeatures);
 
 protected:
     // the font this shaper is working with
     gfxFont * mFont;
 };
 
 /* a SPECIFIC single font family */
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxFontFeatures.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFontFeatures.h"
+#include "nsUnicharUtils.h"
+#include "nsHashKeys.h"
+
+using namespace mozilla;
+
+gfxFontFeatureValueSet::gfxFontFeatureValueSet()
+{
+    mFontFeatureValues.Init(10);
+}
+
+bool
+gfxFontFeatureValueSet::GetFontFeatureValuesFor(const nsAString& aFamily,
+                                                uint32_t aVariantProperty,
+                                                const nsAString& aName,
+                                                nsTArray<uint32_t>& aValues)
+{
+    nsAutoString family(aFamily), name(aName);
+    ToLowerCase(family);
+    ToLowerCase(name);
+    FeatureValueHashKey key(family, aVariantProperty, name);
+
+    aValues.Clear();
+    FeatureValueHashEntry *entry = mFontFeatureValues.GetEntry(key);
+    if (entry) {
+        NS_ASSERTION(entry->mValues.Length() > 0,
+                     "null array of font feature values");
+        aValues.AppendElements(entry->mValues);
+        return true;
+    }
+
+    return false;
+}
+
+
+void
+gfxFontFeatureValueSet::AddFontFeatureValues(const nsAString& aFamily,
+                 const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aValues)
+{
+    nsAutoString family(aFamily);
+    ToLowerCase(family);
+
+    uint32_t i, numFeatureValues = aValues.Length();
+    for (i = 0; i < numFeatureValues; i++) {
+        const FeatureValues& fv = aValues.ElementAt(i);
+        uint32_t alternate = fv.alternate;
+        uint32_t j, numValues = fv.valuelist.Length();
+        for (j = 0; j < numValues; j++) {
+            const ValueList& v = fv.valuelist.ElementAt(j);
+            nsAutoString name(v.name);
+            ToLowerCase(name);
+            FeatureValueHashKey key(family, alternate, name);
+            FeatureValueHashEntry *entry = mFontFeatureValues.PutEntry(key);
+            entry->mKey = key;
+            entry->mValues = v.featureSelectors;
+        }
+    }
+}
+
+bool
+gfxFontFeatureValueSet::FeatureValueHashEntry::KeyEquals(
+                                               const KeyTypePointer aKey) const
+{
+    return aKey->mPropVal == mKey.mPropVal &&
+           aKey->mFamily.Equals(mKey.mFamily) &&
+           aKey->mName.Equals(mKey.mName);
+}
+
+PLDHashNumber
+gfxFontFeatureValueSet::FeatureValueHashEntry::HashKey(
+                                                     const KeyTypePointer aKey)
+{
+    return HashString(aKey->mFamily) + HashString(aKey->mName) +
+           aKey->mPropVal * uint32_t(0xdeadbeef);
+}
+
--- a/gfx/thebes/gfxFontFeatures.h
+++ b/gfx/thebes/gfxFontFeatures.h
@@ -2,16 +2,21 @@
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_FONT_FEATURES_H
 #define GFX_FONT_FEATURES_H
 
+#include "gfxTypes.h"
+#include "nsTHashtable.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
 // An OpenType feature tag and value pair
 struct gfxFontFeature {
     uint32_t mTag; // see http://www.microsoft.com/typography/otspec/featuretags.htm
     uint32_t mValue; // 0 = off, 1 = on, larger values may be used as parameters
                      // to features that select among multiple alternatives
 };
 
 inline bool
@@ -21,9 +26,100 @@ operator<(const gfxFontFeature& a, const
 }
 
 inline bool
 operator==(const gfxFontFeature& a, const gfxFontFeature& b)
 {
     return (a.mTag == b.mTag) && (a.mValue == b.mValue);
 }
 
+struct gfxAlternateValue {
+    uint32_t           alternate;  // constants in gfxFontConstants.h
+    nsString           value;      // string value to be looked up
+};
+
+inline bool
+operator<(const gfxAlternateValue& a, const gfxAlternateValue& b)
+{
+    return (a.alternate < b.alternate) ||
+        ((a.alternate == b.alternate) && (a.value < b.value));
+}
+
+inline bool
+operator==(const gfxAlternateValue& a, const gfxAlternateValue& b)
+{
+    return (a.alternate == b.alternate) && (a.value == b.value);
+}
+
+class THEBES_API gfxFontFeatureValueSet {
+public:
+    NS_INLINE_DECL_REFCOUNTING(gfxFontFeatureValueSet)
+
+    gfxFontFeatureValueSet();
+    virtual ~gfxFontFeatureValueSet() {}
+
+    struct ValueList {
+        ValueList(const nsAString& aName, const nsTArray<uint32_t>& aSelectors)
+          : name(aName), featureSelectors(aSelectors)
+        {}
+        nsString             name;
+        nsTArray<uint32_t>   featureSelectors;
+    };
+
+    struct FeatureValues {
+        uint32_t             alternate;
+        nsTArray<ValueList>  valuelist;
+    };
+
+    // returns true if found, false otherwise
+    bool
+    GetFontFeatureValuesFor(const nsAString& aFamily,
+                            uint32_t aVariantProperty,
+                            const nsAString& aName,
+                            nsTArray<uint32_t>& aValues);
+    void
+    AddFontFeatureValues(const nsAString& aFamily,
+                const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aValues);
+
+protected:
+    struct FeatureValueHashKey {
+        nsString mFamily;
+        uint32_t mPropVal;
+        nsString mName;
+
+        FeatureValueHashKey()
+            : mPropVal(0)
+        { }
+        FeatureValueHashKey(const nsAString& aFamily,
+                            uint32_t aPropVal,
+                            const nsAString& aName)
+            : mFamily(aFamily), mPropVal(aPropVal), mName(aName)
+        { }
+        FeatureValueHashKey(const FeatureValueHashKey& aKey)
+            : mFamily(aKey.mFamily), mPropVal(aKey.mPropVal), mName(aKey.mName)
+        { }
+    };
+
+    class FeatureValueHashEntry : public PLDHashEntryHdr {
+    public:
+        typedef const FeatureValueHashKey &KeyType;
+        typedef const FeatureValueHashKey *KeyTypePointer;
+
+        FeatureValueHashEntry(KeyTypePointer aKey) { }
+        FeatureValueHashEntry(const FeatureValueHashEntry& toCopy)
+        {
+            NS_ERROR("Should not be called");
+        }
+        ~FeatureValueHashEntry() { }
+
+        bool KeyEquals(const KeyTypePointer aKey) const;
+        static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+        static PLDHashNumber HashKey(const KeyTypePointer aKey);
+        enum { ALLOW_MEMMOVE = true };
+
+        FeatureValueHashKey mKey;
+        nsTArray<uint32_t>  mValues;
+    };
+
+    nsTHashtable<FeatureValueHashEntry> mFontFeatureValues;
+  };
+
 #endif
--- a/gfx/thebes/gfxGraphiteShaper.cpp
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -187,19 +187,21 @@ gfxGraphiteShaper::ShapeText(gfxContext 
         nsAutoCString langString;
         style->language->ToUTF8String(langString);
         grLang = GetGraphiteTagForLang(langString);
     }
     gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
 
     nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
 
+    // if style contains font-specific features
     if (MergeFontFeatures(style,
                           mFont->GetFontEntry()->mFeatureSettings,
                           aShapedText->DisableLigatures(),
+                          mFont->GetFontEntry()->FamilyName(),
                           mergedFeatures))
     {
         // enumerate result and insert into Graphite feature list
         GrFontFeatures f = {mGrFace, grFeatures};
         mergedFeatures.Enumerate(AddFeature, &f);
     }
 
     size_t numChars = gr_count_unicode_characters(gr_utf16,
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -976,16 +976,17 @@ gfxHarfBuzzShaper::ShapeText(gfxContext 
 
     gfxFontEntry *entry = mFont->GetFontEntry();
 
     nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
 
     if (MergeFontFeatures(style,
                           mFont->GetFontEntry()->mFeatureSettings,
                           aShapedText->DisableLigatures(),
+                          mFont->GetFontEntry()->FamilyName(),
                           mergedFeatures))
     {
         // enumerate result and insert into hb_feature array
         mergedFeatures.Enumerate(AddFeature, &features);
     }
 
     bool isRightToLeft = aShapedText->IsRightToLeft();
     hb_buffer_t *buffer = hb_buffer_create();