Bug 1024804 p1 - implement fallback for font-variant-position sub/superscript glyphs. r=jfkthame
authorJohn Daggett <jdaggett@mozilla.com>
Tue, 22 Jul 2014 09:02:45 +0900
changeset 195419 7d18d41e8a090517c0e910edf41142ac41ec321c
parent 195418 6e1ee059db8869426090a83c5d82bf23abecdf16
child 195420 4cb44d54d48cdbb3096cc7ec28605ce6a2084261
push id27182
push useremorley@mozilla.com
push dateTue, 22 Jul 2014 16:21:27 +0000
treeherdermozilla-central@fe7c119a55e2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs1024804
milestone33.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 1024804 p1 - implement fallback for font-variant-position sub/superscript glyphs. r=jfkthame
gfx/src/nsFont.cpp
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxFontConstants.h
gfx/thebes/gfxPangoFonts.cpp
--- a/gfx/src/nsFont.cpp
+++ b/gfx/src/nsFont.cpp
@@ -263,17 +263,16 @@ void nsFont::AddFontFeaturesToStyle(gfxF
 
   // -- 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
   // passed into gfxFontStyle to deal with appropriate fallback.
   // for now, font-variant setting overrides font-variant-caps
@@ -327,32 +326,22 @@ void nsFont::AddFontFeaturesToStyle(gfxF
   if (variantNumeric) {
     AddFontFeaturesBitmask(variantNumeric,
                            NS_FONT_VARIANT_NUMERIC_LINING,
                            NS_FONT_VARIANT_NUMERIC_ORDINAL,
                            numericDefaults, aStyle->featureSettings);
   }
 
   // -- position
-  setting.mTag = 0;
-  setting.mValue = 1;
-  switch (variantPosition) {
-    case NS_FONT_VARIANT_POSITION_SUPER:
-      setting.mTag = TRUETYPE_TAG('s','u','p','s');
-      aStyle->featureSettings.AppendElement(setting);
-      break;
+  aStyle->variantSubSuper = variantPosition;
 
-    case NS_FONT_VARIANT_POSITION_SUB:
-      setting.mTag = TRUETYPE_TAG('s','u','b','s');
-      aStyle->featureSettings.AppendElement(setting);
-      break;
-
-    default:
-      break;
-  }
+  // indicate common-path case when neither variantCaps or variantSubSuper are set
+  aStyle->noFallbackVariantFeatures =
+    (aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL) &&
+    (variantPosition == NS_FONT_VARIANT_POSITION_NORMAL);
 
   // add in features from font-feature-settings
   aStyle->featureSettings.AppendElements(fontFeatureSettings);
 
   // enable grayscale antialiasing for text
   if (smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
     aStyle->useGrayscaleAntialiasing = true;
   }
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -30,16 +30,17 @@
 #include "gfxScriptItemizer.h"
 #include "nsSpecialCasingData.h"
 #include "nsTextRunTransformations.h"
 #include "nsUnicodeProperties.h"
 #include "nsMathUtils.h"
 #include "nsBidiUtils.h"
 #include "nsUnicodeRange.h"
 #include "nsStyleConsts.h"
+#include "mozilla/AppUnits.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "gfxSVGGlyphs.h"
 #include "gfxMathTable.h"
@@ -894,20 +895,24 @@ PR_STATIC_ASSERT(MOZ_NUM_SCRIPT_CODES <=
 
 bool
 gfxFontEntry::SupportsOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag)
 {
     if (!mSupportedFeatures) {
         mSupportedFeatures = new nsDataHashtable<nsUint32HashKey,bool>();
     }
 
+    // note: high-order three bytes *must* be unique for each feature
+    // listed below (see SCRIPT_FEATURE macro def'n)
     NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') ||
                  aFeatureTag == HB_TAG('c','2','s','c') ||
                  aFeatureTag == HB_TAG('p','c','a','p') ||
-                 aFeatureTag == HB_TAG('c','2','p','c'),
+                 aFeatureTag == HB_TAG('c','2','p','c') ||
+                 aFeatureTag == HB_TAG('s','u','p','s') ||
+                 aFeatureTag == HB_TAG('s','u','b','s'),
                  "use of unknown feature tag");
 
     // note: graphite feature support uses the last script index
     NS_ASSERTION(aScript < FEATURE_SCRIPT_MASK - 1,
                  "need to bump the size of the feature shift");
 
     uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
     bool result;
@@ -974,20 +979,24 @@ gfxFontEntry::SupportsOpenTypeFeature(in
 
 bool
 gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag)
 {
     if (!mSupportedFeatures) {
         mSupportedFeatures = new nsDataHashtable<nsUint32HashKey,bool>();
     }
 
+    // note: high-order three bytes *must* be unique for each feature
+    // listed below (see SCRIPT_FEATURE macro def'n)
     NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') ||
                  aFeatureTag == HB_TAG('c','2','s','c') ||
                  aFeatureTag == HB_TAG('p','c','a','p') ||
-                 aFeatureTag == HB_TAG('c','2','p','c'),
+                 aFeatureTag == HB_TAG('c','2','p','c') ||
+                 aFeatureTag == HB_TAG('s','u','p','s') ||
+                 aFeatureTag == HB_TAG('s','u','b','s'),
                  "use of unknown feature tag");
 
     // graphite feature check uses the last script slot
     uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag);
     bool result;
     if (mSupportedFeatures->Get(scriptFeature, &result)) {
         return result;
     }
@@ -2133,16 +2142,17 @@ gfxFontShaper::MergeFontFeatures(
     const nsTArray<gfxFontFeature>& styleRuleFeatures =
         aStyle->featureSettings;
 
     // bail immediately if nothing to do
     if (styleRuleFeatures.IsEmpty() &&
         aFontFeatures.IsEmpty() &&
         !aDisableLigatures &&
         aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL &&
+        aStyle->variantSubSuper == NS_FONT_VARIANT_POSITION_NORMAL &&
         numAlts == 0) {
         return false;
     }
 
     // Ligature features are enabled by default in the generic shaper,
     // so we explicitly turn them off if necessary (for letter-spacing)
     if (aDisableLigatures) {
         aMergedFeatures.Put(HB_TAG('l','i','g','a'), 0);
@@ -2185,16 +2195,28 @@ gfxFontShaper::MergeFontFeatures(
         case NS_FONT_VARIANT_CAPS_UNICASE:
             aMergedFeatures.Put(HB_TAG('u','n','i','c'), 1);
             break;
 
         default:
             break;
     }
 
+    // font-variant-position - handled here due to the need for fallback
+    switch (aStyle->variantSubSuper) {
+        case NS_FONT_VARIANT_POSITION_SUPER:
+            aMergedFeatures.Put(HB_TAG('s','u','p','s'), 1);
+            break;
+        case NS_FONT_VARIANT_POSITION_SUB:
+            aMergedFeatures.Put(HB_TAG('s','u','b','s'), 1);
+            break;
+        default:
+            break;
+    }
+
     // 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);
 
@@ -2835,16 +2857,33 @@ gfxFont::SupportsVariantCaps(int32_t aSc
 
     NS_ASSERTION(!(!ok && aFallbackToSmallCaps),
                  "if we found a usable fallback, that counts as ok");
 
     return ok;
 }
 
 bool
+gfxFont::SupportsSubSuperscript(int32_t aScript, uint32_t aSubSuperscript)
+{
+    bool ok = false;
+    switch (aSubSuperscript) {
+        case NS_FONT_VARIANT_POSITION_SUPER:
+            ok = SupportsFeature(aScript, HB_TAG('s','u','p','s'));
+            break;
+        case NS_FONT_VARIANT_POSITION_SUB:
+            ok = SupportsFeature(aScript, HB_TAG('s','u','b','s'));
+            break;
+        default:
+            break;
+    }
+    return ok;
+}
+
+bool
 gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn)
 {
     aFeatureOn = false;
 
     if (mStyle.featureSettings.IsEmpty() &&
         GetFontEntry()->mFeatureSettings.IsEmpty()) {
         return false;
     }
@@ -3184,16 +3223,20 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
             strikes = NS_lroundf(GetSyntheticBoldOffset() / xscale);
         }
     }
 
     uint32_t i;
     // Current position in appunits
     double x = aPt->x;
     double y = aPt->y;
+    double origY = aPt->y;
+    if (mStyle.baselineOffset != 0.0) {
+        y += mStyle.baselineOffset * appUnitsPerDevUnit;
+    }
 
     RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
 
     bool paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs;
     bool emittedGlyphs = false;
 
     {
       RefPtr<ScaledFont> scaledFont = GetScaledFont(dt);
@@ -3443,17 +3486,17 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
           aCallbacks->NotifyGlyphPathEmitted();
       }
 
       dt->SetTransform(oldMat);
 
       dt->SetPermitSubpixelAA(oldSubpixelAA);
     }
 
-    *aPt = gfxPoint(x, y);
+    *aPt = gfxPoint(x, origY);
 }
 
 bool
 gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode,
                         uint32_t aGlyphId, gfxTextContextPaint *aContextPaint)
 {
     if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
         return false;
@@ -5275,100 +5318,116 @@ gfxFontGroup::InitTextRun(gfxContext *aC
     }
 
 #ifdef PR_LOGGING
     PRLogModuleInfo *log = (mStyle.systemFont ?
                             gfxPlatform::GetLog(eGfxLog_textrunui) :
                             gfxPlatform::GetLog(eGfxLog_textrun));
 #endif
 
-    if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
-
-#ifdef PR_LOGGING
-        if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
-            nsAutoCString lang;
-            mStyle.language->ToUTF8String(lang);
-            nsAutoString families;
-            mFamilyList.ToString(families);
-            nsAutoCString str((const char*)aString, aLength);
-            PR_LOG(log, PR_LOG_WARNING,\
-                   ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
-                    "len %d weight: %d width: %d style: %s size: %6.2f %d-byte "
-                    "TEXTRUN [%s] ENDTEXTRUN\n",
-                    (mStyle.systemFont ? "textrunui" : "textrun"),
-                    NS_ConvertUTF16toUTF8(families).get(),
-                    (mFamilyList.GetDefaultFontType() == eFamily_serif ?
-                     "serif" :
-                     (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
-                      "sans-serif" : "none")),
-                    lang.get(), MOZ_SCRIPT_LATIN, aLength,
-                    uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
-                    (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
-                    (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
-                                                            "normal")),
-                    mStyle.size,
-                    sizeof(T),
-                    str.get()));
-        }
-#endif
-
-        // the text is still purely 8-bit; bypass the script-run itemizer
-        // and treat it as a single Latin run
-        InitScriptRun(aContext, aTextRun, aString,
-                      0, aLength, MOZ_SCRIPT_LATIN);
-    } else {
-        const char16_t *textPtr;
-        if (transformedString) {
-            textPtr = transformedString.get();
-        } else {
-            // typecast to avoid compilation error for the 8-bit version,
-            // even though this is dead code in that case
-            textPtr = reinterpret_cast<const char16_t*>(aString);
-        }
-
-        // split into script runs so that script can potentially influence
-        // the font matching process below
-        gfxScriptItemizer scriptRuns(textPtr, aLength);
-
-        uint32_t runStart = 0, runLimit = aLength;
-        int32_t runScript = MOZ_SCRIPT_LATIN;
-        while (scriptRuns.Next(runStart, runLimit, runScript)) {
+    // variant fallback handling may end up passing through this twice
+    bool redo;
+    do {
+        redo = false;
+
+        if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
 
 #ifdef PR_LOGGING
             if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
                 nsAutoCString lang;
                 mStyle.language->ToUTF8String(lang);
                 nsAutoString families;
                 mFamilyList.ToString(families);
-                uint32_t runLen = runLimit - runStart;
+                nsAutoCString str((const char*)aString, aLength);
                 PR_LOG(log, PR_LOG_WARNING,\
                        ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
-                        "len %d weight: %d width: %d style: %s size: %6.2f "
-                        "%d-byte TEXTRUN [%s] ENDTEXTRUN\n",
+                        "len %d weight: %d width: %d style: %s size: %6.2f %d-byte "
+                        "TEXTRUN [%s] ENDTEXTRUN\n",
                         (mStyle.systemFont ? "textrunui" : "textrun"),
                         NS_ConvertUTF16toUTF8(families).get(),
                         (mFamilyList.GetDefaultFontType() == eFamily_serif ?
                          "serif" :
                          (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
                           "sans-serif" : "none")),
-                        lang.get(), runScript, runLen,
+                        lang.get(), MOZ_SCRIPT_LATIN, aLength,
                         uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
                         (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
                         (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
                                                                 "normal")),
                         mStyle.size,
                         sizeof(T),
-                        NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
+                        str.get()));
             }
 #endif
 
-            InitScriptRun(aContext, aTextRun, textPtr + runStart,
-                          runStart, runLimit - runStart, runScript);
-        }
-    }
+            // the text is still purely 8-bit; bypass the script-run itemizer
+            // and treat it as a single Latin run
+            InitScriptRun(aContext, aTextRun, aString,
+                          0, aLength, MOZ_SCRIPT_LATIN);
+        } else {
+            const char16_t *textPtr;
+            if (transformedString) {
+                textPtr = transformedString.get();
+            } else {
+                // typecast to avoid compilation error for the 8-bit version,
+                // even though this is dead code in that case
+                textPtr = reinterpret_cast<const char16_t*>(aString);
+            }
+
+            // split into script runs so that script can potentially influence
+            // the font matching process below
+            gfxScriptItemizer scriptRuns(textPtr, aLength);
+
+            uint32_t runStart = 0, runLimit = aLength;
+            int32_t runScript = MOZ_SCRIPT_LATIN;
+            while (scriptRuns.Next(runStart, runLimit, runScript)) {
+
+    #ifdef PR_LOGGING
+                if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
+                    nsAutoCString lang;
+                    mStyle.language->ToUTF8String(lang);
+                    nsAutoString families;
+                    mFamilyList.ToString(families);
+                    uint32_t runLen = runLimit - runStart;
+                    PR_LOG(log, PR_LOG_WARNING,\
+                           ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
+                            "len %d weight: %d width: %d style: %s size: %6.2f "
+                            "%d-byte TEXTRUN [%s] ENDTEXTRUN\n",
+                            (mStyle.systemFont ? "textrunui" : "textrun"),
+                            NS_ConvertUTF16toUTF8(families).get(),
+                            (mFamilyList.GetDefaultFontType() == eFamily_serif ?
+                             "serif" :
+                             (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
+                              "sans-serif" : "none")),
+                            lang.get(), runScript, runLen,
+                            uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
+                            (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
+                            (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
+                                                                    "normal")),
+                            mStyle.size,
+                            sizeof(T),
+                            NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
+                }
+    #endif
+
+                InitScriptRun(aContext, aTextRun, textPtr + runStart,
+                              runStart, runLimit - runStart, runScript);
+            }
+        }
+
+        // if shaping was aborted due to lack of feature support, clear out
+        // glyph runs and redo shaping with fallback forced on
+        if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) {
+            redo = true;
+            aTextRun->SetShapingState(
+                gfxTextRun::eShapingState_ForceFallbackFeature);
+            aTextRun->ClearGlyphsAndCharacters();
+        }
+
+    } while (redo);
 
     if (sizeof(T) == sizeof(char16_t) && aLength > 0) {
         gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs();
         if (!glyph->IsSimpleGlyph()) {
             glyph->SetClusterStart(true);
         }
     }
 
@@ -5393,57 +5452,116 @@ gfxFontGroup::InitScriptRun(gfxContext *
                             const T *aString, // text for this script run,
                                               // not the entire textrun
                             uint32_t aOffset, // position of the script run
                                               // within the textrun
                             uint32_t aLength, // length of the script run
                             int32_t aRunScript)
 {
     NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run");
+    NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted,
+                 "don't call InitScriptRun with aborted shaping state");
 
     gfxFont *mainFont = GetFontAt(0);
 
     uint32_t runStart = 0;
     nsAutoTArray<gfxTextRange,3> fontRanges;
     ComputeRanges(fontRanges, aString, aLength, aRunScript);
     uint32_t numRanges = fontRanges.Length();
 
     for (uint32_t r = 0; r < numRanges; r++) {
         const gfxTextRange& range = fontRanges[r];
         uint32_t matchedLength = range.Length();
         gfxFont *matchedFont = range.font;
 
         // create the glyph run for this range
-        if (matchedFont) {
-            bool needsFakeSmallCaps = false;
+        if (matchedFont && mStyle.noFallbackVariantFeatures) {
+            // common case - just do glyph layout and record the
+            // resulting positioned glyphs
+            aTextRun->AddGlyphRun(matchedFont, range.matchType,
+                                  aOffset + runStart, (matchedLength > 0));
+            if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun,
+                                                  aString + runStart,
+                                                  aOffset + runStart,
+                                                  matchedLength,
+                                                  aRunScript)) {
+                // glyph layout failed! treat as missing glyphs
+                matchedFont = nullptr;
+            }
+        } else if (matchedFont) {
+            // shape with some variant feature that requires fallback handling
             bool petiteToSmallCaps = false;
             bool syntheticLower = false;
             bool syntheticUpper = false;
 
-            if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) {
-                needsFakeSmallCaps =
-                    !matchedFont->SupportsVariantCaps(aRunScript,
-                        mStyle.variantCaps, petiteToSmallCaps,
-                        syntheticLower, syntheticUpper);
-            }
-            if (needsFakeSmallCaps) {
+            if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL &&
+                (aTextRun->GetShapingState() ==
+                     gfxTextRun::eShapingState_ForceFallbackFeature ||
+                 !matchedFont->SupportsSubSuperscript(aRunScript,
+                                                      mStyle.variantSubSuper)))
+            {
+                // fallback for subscript/superscript variant glyphs
+
+                // if the feature was already used, abort and force
+                // fallback across the entire textrun
+                gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
+
+                if (ss == gfxTextRun::eShapingState_Normal) {
+                    aTextRun->SetShapingState(gfxTextRun::eShapingState_ShapingWithFallback);
+                } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) {
+                    aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
+                    return;
+                }
+
+                nsRefPtr<gfxFont> subSuperFont =
+                    matchedFont->GetSubSuperscriptFont(aTextRun->GetAppUnitsPerDevUnit());
+                aTextRun->AddGlyphRun(subSuperFont, range.matchType,
+                                      aOffset + runStart, (matchedLength > 0));
+                if (!subSuperFont->SplitAndInitTextRun(aContext, aTextRun,
+                                                       aString + runStart,
+                                                       aOffset + runStart,
+                                                       matchedLength,
+                                                       aRunScript)) {
+                    // glyph layout failed! treat as missing glyphs
+                    matchedFont = nullptr;
+                }
+            } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL &&
+                       !matchedFont->SupportsVariantCaps(aRunScript,
+                                                         mStyle.variantCaps,
+                                                         petiteToSmallCaps,
+                                                         syntheticLower,
+                                                         syntheticUpper))
+            {
+                // fallback for small-caps variant glyphs
                 if (!matchedFont->InitFakeSmallCapsRun(aContext, aTextRun,
                                                        aString + runStart,
                                                        aOffset + runStart,
                                                        matchedLength,
                                                        range.matchType,
                                                        aRunScript,
                                                        syntheticLower,
                                                        syntheticUpper)) {
                     matchedFont = nullptr;
                 }
             } else {
+                // shape normally with variant feature enabled
+                gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
+
+                // adjust the shaping state if necessary
+                if (ss == gfxTextRun::eShapingState_Normal) {
+                    aTextRun->SetShapingState(gfxTextRun::eShapingState_ShapingWithFeature);
+                } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) {
+                    // already have shaping results using fallback, need to redo
+                    aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
+                    return;
+                }
+
+                // do glyph layout and record the resulting positioned glyphs
                 aTextRun->AddGlyphRun(matchedFont, range.matchType,
                                       aOffset + runStart, (matchedLength > 0));
-                // do glyph layout and record the resulting positioned glyphs
                 if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun,
                                                       aString + runStart,
                                                       aOffset + runStart,
                                                       matchedLength,
                                                       aRunScript)) {
                     // glyph layout failed! treat as missing glyphs
                     matchedFont = nullptr;
                 }
@@ -5713,16 +5831,60 @@ gfxFont::GetSmallCapsFont()
     gfxFontStyle style(*GetStyle());
     style.size *= SMALL_CAPS_SCALE_FACTOR;
     style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
     gfxFontEntry* fe = GetFontEntry();
     bool needsBold = style.weight >= 600 && !fe->IsBold();
     return fe->FindOrMakeFont(&style, needsBold);
 }
 
+void
+gfxFont::CalculateSubSuperSizeAndOffset(int32_t aAppUnitsPerDevPixel,
+                                        gfxFloat& aSubSuperSizeRatio,
+                                        float& aBaselineOffset)
+{
+    gfxFloat cssPixelSize = mStyle.size * aAppUnitsPerDevPixel /
+                                AppUnitsPerCSSPixel();
+
+    // calculate reduced size, roughly mimicing behavior of font-size: smaller
+    if (cssPixelSize < NS_FONT_SUB_SUPER_SMALL_SIZE) {
+        aSubSuperSizeRatio = NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL;
+    } else if (cssPixelSize >= NS_FONT_SUB_SUPER_SMALL_SIZE) {
+        aSubSuperSizeRatio = NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+    } else {
+        gfxFloat t = (cssPixelSize - NS_FONT_SUB_SUPER_SMALL_SIZE) /
+                         (NS_FONT_SUB_SUPER_LARGE_SIZE -
+                          NS_FONT_SUB_SUPER_SMALL_SIZE);
+        aSubSuperSizeRatio = (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL +
+                            t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+    }
+
+    // calculate the baseline offset
+    if (mStyle.variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) {
+        aBaselineOffset = mStyle.size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO;
+    } else {
+        aBaselineOffset = mStyle.size * NS_FONT_SUBSCRIPT_OFFSET_RATIO;
+    }
+}
+
+already_AddRefed<gfxFont>
+gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
+{
+    gfxFontStyle style(*GetStyle());
+    gfxFloat subSuperSizeRatio;
+    CalculateSubSuperSizeAndOffset(aAppUnitsPerDevPixel,
+                                   subSuperSizeRatio,
+                                   style.baselineOffset);
+    style.size *= subSuperSizeRatio;
+    style.variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
+    gfxFontEntry* fe = GetFontEntry();
+    bool needsBold = style.weight >= 600 && !fe->IsBold();
+    return fe->FindOrMakeFont(&style, needsBold);
+}
+
 gfxTextRun *
 gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel,
                                  LazyReferenceContextGetter& aRefContextGetter)
 {
     if (mCachedEllipsisTextRun &&
         mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) {
         return mCachedEllipsisTextRun;
     }
@@ -6152,43 +6314,47 @@ gfxFontStyle::ParseFontLanguageOverride(
   while (index++ < 4) {
     result = (result << 8) + 0x20;
   }
   return result;
 }
 
 gfxFontStyle::gfxFontStyle() :
     language(nsGkAtoms::x_western),
-    size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f),
+    size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f), baselineOffset(0.0f),
     languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL),
     systemFont(true), printerFont(false), useGrayscaleAntialiasing(false),
     style(NS_FONT_STYLE_NORMAL),
     allowSyntheticWeight(true), allowSyntheticStyle(true),
-    variantCaps(NS_FONT_VARIANT_CAPS_NORMAL)
+    noFallbackVariantFeatures(true),
+    variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+    variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL)
 {
 }
 
 gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
                            gfxFloat aSize, nsIAtom *aLanguage,
                            float aSizeAdjust, bool aSystemFont,
                            bool aPrinterFont,
                            bool aAllowWeightSynthesis,
                            bool aAllowStyleSynthesis,
                            const nsString& aLanguageOverride):
     language(aLanguage),
-    size(aSize), sizeAdjust(aSizeAdjust),
+    size(aSize), sizeAdjust(aSizeAdjust), baselineOffset(0.0f),
     languageOverride(ParseFontLanguageOverride(aLanguageOverride)),
     weight(aWeight), stretch(aStretch),
     systemFont(aSystemFont), printerFont(aPrinterFont),
     useGrayscaleAntialiasing(false),
     style(aStyle),
     allowSyntheticWeight(aAllowWeightSynthesis),
     allowSyntheticStyle(aAllowStyleSynthesis),
-    variantCaps(NS_FONT_VARIANT_CAPS_NORMAL)
+    noFallbackVariantFeatures(true),
+    variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+    variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL)
 {
     MOZ_ASSERT(!mozilla::IsNaN(size));
     MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
 
     if (weight > 900)
         weight = 900;
     if (weight < 100)
         weight = 100;
@@ -6206,24 +6372,27 @@ gfxFontStyle::gfxFontStyle(uint8_t aStyl
         language = nsGkAtoms::x_western;
     }
 }
 
 gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) :
     language(aStyle.language),
     featureValueLookup(aStyle.featureValueLookup),
     size(aStyle.size), sizeAdjust(aStyle.sizeAdjust),
+    baselineOffset(aStyle.baselineOffset),
     languageOverride(aStyle.languageOverride),
     weight(aStyle.weight), stretch(aStyle.stretch),
     systemFont(aStyle.systemFont), printerFont(aStyle.printerFont),
     useGrayscaleAntialiasing(aStyle.useGrayscaleAntialiasing),
     style(aStyle.style),
     allowSyntheticWeight(aStyle.allowSyntheticWeight),
     allowSyntheticStyle(aStyle.allowSyntheticStyle),
-    variantCaps(aStyle.variantCaps)
+    noFallbackVariantFeatures(aStyle.noFallbackVariantFeatures),
+    variantCaps(aStyle.variantCaps),
+    variantSubSuper(aStyle.variantSubSuper)
 {
     featureSettings.AppendElements(aStyle.featureSettings);
     alternateValues.AppendElements(aStyle.alternateValues);
 }
 
 int8_t
 gfxFontStyle::ComputeWeight() const
 {
@@ -6484,16 +6653,17 @@ gfxTextRun::Create(const gfxTextRunFacto
 }
 
 gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams,
                        uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags)
     : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit)
     , mUserData(aParams->mUserData)
     , mFontGroup(aFontGroup)
     , mReleasedFontGroup(false)
+    , mShapingState(eShapingState_Normal)
 {
     NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale");
     MOZ_COUNT_CTOR(gfxTextRun);
     NS_ADDREF(mFontGroup);
 
 #ifndef RELEASE_BUILD
     gfxTextPerfMetrics *tp = aFontGroup->GetTextPerfMetrics();
     if (tp) {
@@ -7549,16 +7719,25 @@ gfxTextRun::CopyGlyphDataFrom(gfxTextRun
         nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType,
                                   start - aStart + aDest, false);
         if (NS_FAILED(rv))
             return;
     }
 }
 
 void
+gfxTextRun::ClearGlyphsAndCharacters()
+{
+    ResetGlyphRuns();
+    memset(reinterpret_cast<char*>(mCharacterGlyphs), 0,
+           mLength * sizeof(CompressedGlyph));
+    mDetailedGlyphs = nullptr;
+}
+
+void
 gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext,
                           uint32_t aCharIndex)
 {
     if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) {
         return;
     }
 
     aFont->InitWordCache();
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -108,16 +108,19 @@ struct gfxFontStyle {
     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;
 
+    // baseline offset, used when simulating sub/superscript glyphs
+    float baselineOffset;
+
     // Language system tag, to override document language;
     // an OpenType "language system" tag represented as a 32-bit integer
     // (see http://www.microsoft.com/typography/otspec/languagetags.htm).
     // Normally 0, so font rendering will use the document or element language
     // (see above) to control any language-specific rendering, but the author
     // can override this for cases where the options implemented in the font
     // do not directly match the actual language. (E.g. lang may be Macedonian,
     // but the font in use does not explicitly support this; the author can
@@ -145,19 +148,26 @@ struct gfxFontStyle {
 
     // The style of font (normal, italic, oblique)
     uint8_t style : 2;
 
     // Whether synthetic styles are allowed
     bool allowSyntheticWeight : 1;
     bool allowSyntheticStyle : 1;
 
+    // some variant features require fallback which complicates the shaping
+    // code, so set up a bool to indicate when shaping with fallback is needed
+    bool noFallbackVariantFeatures : 1;
+
     // caps variant (small-caps, petite-caps, etc.)
     uint8_t variantCaps;
 
+    // sub/superscript variant
+    uint8_t variantSubSuper;
+
     // Return the final adjusted font size for the given aspect ratio.
     // Not meant to be called when sizeAdjust = 0.
     gfxFloat GetAdjustedSize(gfxFloat aspect) const {
         NS_ASSERTION(sizeAdjust != 0.0, "Not meant to be called when sizeAdjust = 0");
         gfxFloat adjustedSize = std::max(NS_round(size*(sizeAdjust/aspect)), 1.0);
         return std::min(adjustedSize, FONT_MAX_SIZE);
     }
 
@@ -170,24 +180,26 @@ struct gfxFontStyle {
     int8_t ComputeWeight() const;
 
     bool Equals(const gfxFontStyle& other) const {
         return
             (*reinterpret_cast<const uint64_t*>(&size) ==
              *reinterpret_cast<const uint64_t*>(&other.size)) &&
             (style == other.style) &&
             (variantCaps == other.variantCaps) &&
+            (variantSubSuper == other.variantSubSuper) &&
             (allowSyntheticWeight == other.allowSyntheticWeight) &&
             (allowSyntheticStyle == other.allowSyntheticStyle) &&
             (systemFont == other.systemFont) &&
             (printerFont == other.printerFont) &&
             (useGrayscaleAntialiasing == other.useGrayscaleAntialiasing) &&
             (weight == other.weight) &&
             (stretch == other.stretch) &&
             (language == other.language) &&
+            (baselineOffset == other.baselineOffset) &&
             (*reinterpret_cast<const uint32_t*>(&sizeAdjust) ==
              *reinterpret_cast<const uint32_t*>(&other.sizeAdjust)) &&
             (featureSettings == other.featureSettings) &&
             (languageOverride == other.languageOverride) &&
             (alternateValues == other.alternateValues) &&
             (featureValueLookup == other.featureValueLookup);
     }
 
@@ -1634,16 +1646,19 @@ public:
 
     // whether the font supports "real" small caps, petite caps etc.
     // aFallbackToSmallCaps true when petite caps should fallback to small caps
     bool SupportsVariantCaps(int32_t aScript, uint32_t aVariantCaps,
                              bool& aFallbackToSmallCaps,
                              bool& aSyntheticLowerToSmallCaps,
                              bool& aSyntheticUpperToSmallCaps);
 
+    // whether the font supports subscript/superscript feature
+    bool SupportsSubSuperscript(int32_t aScript, uint32_t aSubSuperscript);
+
     // Subclasses may choose to look up glyph ids for characters.
     // If they do not override this, gfxHarfBuzzShaper will fetch the cmap
     // table and use that.
     virtual bool ProvidesGetGlyph() const {
         return false;
     }
     // Map unicode character to glyph ID.
     // Only used if ProvidesGetGlyph() returns true.
@@ -1977,17 +1992,28 @@ public:
     // Get a dimensionless math constant (e.g. a percentage);
     // may only be called if mFontEntry->TryGetMathTable has succeeded
     // (i.e. the font is known to be a valid OpenType math font).
     float GetMathConstant(gfxFontEntry::MathConstant aConstant)
     {
         return mFontEntry->GetMathConstant(aConstant);
     }
 
+    // return a cloned font resized and offset to simulate sub/superscript glyphs
+    virtual already_AddRefed<gfxFont>
+    GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel);
+
 protected:
+
+    // set the font size and offset used for
+    // synthetic subscript/superscript glyphs
+    void CalculateSubSuperSizeAndOffset(int32_t aAppUnitsPerDevPixel,
+                                        gfxFloat& aSubSuperSizeRatio,
+                                        float& aBaselineOffset);
+
     // Return a font that is a "clone" of this one, but reduced to 80% size
     // (and with variantCaps set to normal).
     // Default implementation relies on gfxFontEntry::CreateFontInstance;
     // backends that don't implement that will need to override this and use
     // an alternative technique. (gfxPangoFonts, I'm looking at you...)
     virtual already_AddRefed<gfxFont> GetSmallCapsFont();
 
     // subclasses may provide (possibly hinted) glyph widths (in font units);
@@ -3300,16 +3326,19 @@ public:
     void SortGlyphRuns();
     void SanitizeGlyphRuns();
 
     CompressedGlyph* GetCharacterGlyphs() {
         NS_ASSERTION(mCharacterGlyphs, "failed to initialize mCharacterGlyphs");
         return mCharacterGlyphs;
     }
 
+    // clean out results from shaping in progress, used for fallback scenarios
+    void ClearGlyphsAndCharacters();
+
     void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, uint32_t aCharIndex);
 
     // Set the glyph data for the given character index to the font's
     // space glyph, IF this can be done as a "simple" glyph record
     // (not requiring a DetailedGlyph entry). This avoids the need to call
     // the font shaper and go through the shaped-word cache for most spaces.
     //
     // The parameter aSpaceChar is the original character code for which
@@ -3421,16 +3450,33 @@ public:
         }
         mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
         return SizeOfIncludingThis(aMallocSizeOf);
     }
     void ResetSizeOfAccountingFlags() {
         mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
     }
 
+    // shaping state - for some font features, fallback is required that
+    // affects the entire run. for example, fallback for one script/font
+    // portion of a textrun requires fallback to be applied to the entire run
+
+    enum ShapingState {
+        eShapingState_Normal,                 // default state
+        eShapingState_ShapingWithFeature,     // have shaped with feature
+        eShapingState_ShapingWithFallback,    // have shaped with fallback
+        eShapingState_Aborted,                // abort initial iteration
+        eShapingState_ForceFallbackFeature    // redo with fallback forced on
+    };
+
+    ShapingState GetShapingState() const { return mShapingState; }
+    void SetShapingState(ShapingState aShapingState) {
+        mShapingState = aShapingState;
+    }
+
 #ifdef DEBUG
     void Dump(FILE* aOutput);
 #endif
 
 protected:
     /**
      * Create a textrun, and set its mCharacterGlyphs to point immediately
      * after the base object; this is ONLY used in conjunction with placement
@@ -3517,16 +3563,20 @@ private:
     gfxSkipChars      mSkipChars;
     nsExpirationState mExpirationState;
 
     bool              mSkipDrawing; // true if the font group we used had a user font
                                     // download that's in progress, so we should hide text
                                     // until the download completes (or timeout fires)
     bool              mReleasedFontGroup; // we already called NS_RELEASE on
                                           // mFontGroup, so don't do it again
+
+    // shaping state for handling variant fallback features
+    // such as subscript/superscript variant glyphs
+    ShapingState      mShapingState;
 };
 
 class gfxFontGroup : public gfxTextRunFactory {
 public:
     class FamilyFace {
     public:
         FamilyFace() { }
 
--- a/gfx/thebes/gfxFontConstants.h
+++ b/gfx/thebes/gfxFontConstants.h
@@ -195,9 +195,18 @@ enum {
 #define NS_FONT_VARIANT_POSITION_NORMAL             0
 #define NS_FONT_VARIANT_POSITION_SUPER              1
 #define NS_FONT_VARIANT_POSITION_SUB                2
 
 // based on fixed offset values used within WebKit
 #define NS_FONT_SUBSCRIPT_OFFSET_RATIO     (0.20)
 #define NS_FONT_SUPERSCRIPT_OFFSET_RATIO   (0.34)
 
+// this roughly corresponds to font-size: smaller behavior
+// at smaller sizes <20px the ratio is closer to 0.8 while at
+// larger sizes >45px the ratio is closer to 0.667 and in between
+// a blend of values is used
+#define NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL       (0.82)
+#define NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE       (0.667)
+#define NS_FONT_SUB_SUPER_SMALL_SIZE             (20.0)
+#define NS_FONT_SUB_SUPER_LARGE_SIZE             (45.0)
+
 #endif
--- a/gfx/thebes/gfxPangoFonts.cpp
+++ b/gfx/thebes/gfxPangoFonts.cpp
@@ -653,17 +653,23 @@ public:
     static already_AddRefed<gfxFcFont>
     GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern,
                   const gfxFontStyle *aFontStyle);
 
 #ifdef USE_SKIA
     virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions> GetGlyphRenderingOptions();
 #endif
 
+    // return a cloned font resized and offset to simulate sub/superscript glyphs
+    virtual already_AddRefed<gfxFont>
+    GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel);
+
 protected:
+    virtual already_AddRefed<gfxFont> MakeScaledFont(gfxFontStyle *aFontStyle,
+                                                     gfxFloat aFontScale);
     virtual already_AddRefed<gfxFont> GetSmallCapsFont();
 
 private:
     gfxFcFont(cairo_scaled_font_t *aCairoFont, gfxFcFontEntry *aFontEntry,
               const gfxFontStyle *aFontStyle);
 
     // key for locating a gfxFcFont corresponding to a cairo_scaled_font
     static cairo_user_data_key_t sGfxFontKey;
@@ -1515,50 +1521,68 @@ gfxFcFont::~gfxFcFont()
 {
     cairo_scaled_font_set_user_data(mScaledFont,
                                     &sGfxFontKey,
                                     nullptr,
                                     nullptr);
 }
 
 already_AddRefed<gfxFont>
-gfxFcFont::GetSmallCapsFont()
+gfxFcFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
 {
     gfxFontStyle style(*GetStyle());
-    style.size *= SMALL_CAPS_SCALE_FACTOR;
-    style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+    gfxFloat subSuperSizeRatio;
+    CalculateSubSuperSizeAndOffset(aAppUnitsPerDevPixel,
+                                   subSuperSizeRatio,
+                                   style.baselineOffset);
+    style.size *= subSuperSizeRatio;
+    style.variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
+    return MakeScaledFont(&style, subSuperSizeRatio);
+}
+
+already_AddRefed<gfxFont>
+gfxFcFont::MakeScaledFont(gfxFontStyle *aFontStyle, gfxFloat aScaleFactor)
+{
     gfxFcFontEntry* fe = static_cast<gfxFcFontEntry*>(GetFontEntry());
-    nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(fe, &style);
+    nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(fe, aFontStyle);
     if (font) {
         return font.forget();
     }
 
     cairo_matrix_t fontMatrix;
     cairo_scaled_font_get_font_matrix(mScaledFont, &fontMatrix);
-    cairo_matrix_scale(&fontMatrix,
-                       SMALL_CAPS_SCALE_FACTOR, SMALL_CAPS_SCALE_FACTOR);
+    cairo_matrix_scale(&fontMatrix, aScaleFactor, aScaleFactor);
 
     cairo_matrix_t ctm;
     cairo_scaled_font_get_ctm(mScaledFont, &ctm);
 
     cairo_font_options_t *options = cairo_font_options_create();
     cairo_scaled_font_get_font_options(mScaledFont, options);
 
-    cairo_scaled_font_t *smallFont =
+    cairo_scaled_font_t *newFont =
         cairo_scaled_font_create(cairo_scaled_font_get_font_face(mScaledFont),
                                  &fontMatrix, &ctm, options);
     cairo_font_options_destroy(options);
 
-    font = new gfxFcFont(smallFont, fe, &style);
+    font = new gfxFcFont(newFont, fe, aFontStyle);
     gfxFontCache::GetCache()->AddNew(font);
-    cairo_scaled_font_destroy(smallFont);
+    cairo_scaled_font_destroy(newFont);
 
     return font.forget();
 }
 
+already_AddRefed<gfxFont>
+gfxFcFont::GetSmallCapsFont()
+{
+    gfxFontStyle style(*GetStyle());
+    style.size *= SMALL_CAPS_SCALE_FACTOR;
+    style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+    return MakeScaledFont(&style, SMALL_CAPS_SCALE_FACTOR);
+}
+
 /* static */ void
 gfxPangoFontGroup::Shutdown()
 {
     // Resetting gFTLibrary in case this is wanted again after a
     // cairo_debug_reset_static_data.
     gFTLibrary = nullptr;
 }