Bug 1454598 - part 2 - Allow variation fonts to record a weight range in gfxFontEntry, and update font-matching to handle ranges. r=jwatt
authorJonathan Kew <jkew@mozilla.com>
Wed, 25 Apr 2018 07:18:23 +0100
changeset 469164 3c05b11ca2b837819a60c4a05ac85822f56c608e
parent 469163 53b86f2f71a8bc7f09b650bc93669f25cef27dc2
child 469165 ee068c7ae8f44f55c0d0cd004cba77b2c402a2db
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs1454598
milestone61.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 1454598 - part 2 - Allow variation fonts to record a weight range in gfxFontEntry, and update font-matching to handle ranges. r=jwatt
accessible/base/TextAttrs.cpp
dom/ipc/PContent.ipdl
gfx/src/FontPropertyTypes.h
gfx/thebes/gfxDWriteFontList.cpp
gfx/thebes/gfxDWriteFontList.h
gfx/thebes/gfxDWriteFonts.cpp
gfx/thebes/gfxFT2FontBase.cpp
gfx/thebes/gfxFT2FontList.cpp
gfx/thebes/gfxFcPlatformFontList.cpp
gfx/thebes/gfxFontEntry.cpp
gfx/thebes/gfxFontEntry.h
gfx/thebes/gfxFontUtils.cpp
gfx/thebes/gfxFontUtils.h
gfx/thebes/gfxGDIFont.cpp
gfx/thebes/gfxGDIFontList.cpp
gfx/thebes/gfxMacFont.cpp
gfx/thebes/gfxMacPlatformFontList.mm
gfx/thebes/gfxUserFontSet.cpp
gfx/thebes/gfxUserFontSet.h
layout/style/FontFaceSet.cpp
--- a/accessible/base/TextAttrs.cpp
+++ b/accessible/base/TextAttrs.cpp
@@ -651,19 +651,20 @@ TextAttrsMgr::FontWeightTextAttr::
     return FontWeight::Bold();
   }
 
   // On Windows, font->GetStyle()->weight will give the same weight as
   // fontEntry->Weight(), the weight of the first font in the font group,
   // which may not be the weight of the font face used to render the
   // characters. On Mac, font->GetStyle()->weight will just give the same
   // number as getComputedStyle(). fontEntry->Weight() will give the weight
-  // of the font face used.
+  // range supported by the font face used, so we clamp the weight that was
+  // requested by style to what is actually supported by the font.
   gfxFontEntry *fontEntry = font->GetFontEntry();
-  return fontEntry->Weight();
+  return fontEntry->Weight().Clamp(font->GetStyle()->weight);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // AutoGeneratedTextAttr
 ////////////////////////////////////////////////////////////////////////////////
 TextAttrsMgr::AutoGeneratedTextAttr::
   AutoGeneratedTextAttr(HyperTextAccessible* aHyperTextAcc,
                         Accessible* aAccessible) :
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -112,17 +112,18 @@ namespace mozilla {
 namespace dom {
 
 // Used on Android/B2G to pass the list of fonts on the device
 // to the child process
 struct FontListEntry {
     nsString  familyName;
     nsString  faceName;
     nsCString filepath;
-    float     weight;
+    float     minWeight;
+    float     maxWeight;
     int16_t   stretch;
     uint8_t   italic;
     uint8_t   index;
 };
 
 // Used on Mac OS X to pass the list of font families (not faces)
 // from chrome to content processes.
 // The entryType field distinguishes several types of font family
--- a/gfx/src/FontPropertyTypes.h
+++ b/gfx/src/FontPropertyTypes.h
@@ -3,20 +3,23 @@
  * 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/. */
 
 /* font specific types shared by both thebes and layout */
 
 #ifndef GFX_FONT_PROPERTY_TYPES_H
 #define GFX_FONT_PROPERTY_TYPES_H
 
+#include <algorithm>
 #include <cstdint>
 #include <cmath>
+#include <utility>
+
 #include "mozilla/Assertions.h"
-#include "nsStringFwd.h"
+#include "nsString.h"
 
 /*
  * This file is separate from gfxFont.h so that layout can include it
  * without bringing in gfxFont.h and everything it includes.
  */
 
 namespace mozilla {
 
@@ -343,12 +346,86 @@ private:
     : FontPropertyValue(aAngle)
   {
   }
 
   static const InternalType kNormal = INT16_MIN;
   static const InternalType kItalic = INT16_MAX;
 };
 
+
+/**
+ * Convenience type to hold a <min, max> pair representing a range of values.
+ *
+ * The min and max are both inclusive, so when min == max the range represents
+ * a single value (not an empty range).
+ */
+template<class T>
+class FontPropertyRange
+{
+public:
+  /**
+   * Construct a range from given minimum and maximum values (inclusive).
+   */
+  FontPropertyRange(T aMin, T aMax)
+    : mValues(aMin, aMax)
+  {
+    MOZ_ASSERT(aMin <= aMax);
+  }
+
+  /**
+   * Construct a range representing a single value (min==max).
+   */
+  explicit FontPropertyRange(T aValue)
+    : mValues(aValue, aValue)
+  {
+  }
+
+  T Min() const { return mValues.first; }
+  T Max() const { return mValues.second; }
+
+  /**
+   * Clamp the given value to this range.
+   *
+   * (We can't use mozilla::Clamp here because it only accepts integral types.)
+   */
+  T Clamp(T aValue) const
+  {
+    return aValue <= Min() ? Min() : (aValue >= Max() ? Max() : aValue);
+  }
+
+  /**
+   * Return whether the range consists of a single unique value.
+   */
+  bool IsSingle() const
+  {
+    return Min() == Max();
+  }
+
+  bool operator==(const FontPropertyRange& aOther) const
+  {
+    return mValues == aOther.mValues;
+  }
+  bool operator!=(const FontPropertyRange& aOther) const
+  {
+    return mValues != aOther.mValues;
+  }
+
+  void ToString(nsACString& aOutString, const char* aDelim = "..") const
+  {
+    aOutString.AppendPrintf("%g", Min().ToFloat());
+    if (!IsSingle()) {
+      aOutString.AppendPrintf("%s%g", aDelim, Max().ToFloat());
+    }
+  }
+
+private:
+  std::pair<T,T> mValues;
+};
+
+typedef FontPropertyRange<FontWeight>     WeightRange;
+typedef FontPropertyRange<FontStretch>    StretchRange;
+typedef FontPropertyRange<FontSlantStyle> StyleRange;
+
 } // namespace mozilla
 
 #endif // GFX_FONT_PROPERTY_TYPES_H
 
--- a/gfx/thebes/gfxDWriteFontList.cpp
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -185,16 +185,19 @@ gfxDWriteFontFamily::FindStyleVariations
         // <em> and <i> should be rendered as italic in the default style.
         if (fullID.EqualsLiteral("Meiryo Italic") ||
             fullID.EqualsLiteral("Meiryo Bold Italic")) {
             continue;
         }
 
         gfxDWriteFontEntry *fe = new gfxDWriteFontEntry(fullID, font, mIsSystemFontFamily);
         fe->SetForceGDIClassic(mForceGDIClassic);
+
+        fe->SetupVariationRanges();
+
         AddFontEntry(fe);
 
         // postscript/fullname if needed
         nsAutoString psname, fullname;
         if (fontInfoShouldHaveFaceNames) {
             aFontInfoData->GetFaceNames(fe->Name(), fullname, psname);
             if (!fullname.IsEmpty()) {
                 fp->AddFullname(fe, fullname);
@@ -214,23 +217,26 @@ gfxDWriteFontFamily::FindStyleVariations
             if (FAILED(hr)) {
                 skipFaceNames = true;
             } else if (fullname.Length() > 0) {
                 fp->AddFullname(fe, fullname);
             }
         }
 
         if (LOG_FONTLIST_ENABLED()) {
+            nsAutoCString weightString;
+            fe->Weight().ToString(weightString);
             LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
-                 " with style: %s weight: %g stretch: %d psname: %s fullname: %s",
+                 " with style: %s weight: %s stretch: %d psname: %s fullname: %s",
                  NS_ConvertUTF16toUTF8(fe->Name()).get(),
                  NS_ConvertUTF16toUTF8(Name()).get(),
                  (fe->IsItalic()) ?
                   "italic" : (fe->IsOblique() ? "oblique" : "normal"),
-                 fe->Weight().ToFloat(), fe->Stretch(),
+                 weightString.get(),
+                 fe->Stretch(),
                  NS_ConvertUTF16toUTF8(psname).get(),
                  NS_ConvertUTF16toUTF8(fullname).get()));
         }
     }
 
     // assume that if no error, all postscript/fullnames were initialized
     if (!skipFaceNames) {
         mFaceNamesInitialized = true;
@@ -664,46 +670,46 @@ gfxDWriteFontEntry::CreateFontInstance(c
     DWRITE_FONT_SIMULATIONS sims =
         aNeedsBold ? DWRITE_FONT_SIMULATIONS_BOLD : DWRITE_FONT_SIMULATIONS_NONE;
     if (HasVariations() && !aFontStyle->variationSettings.IsEmpty()) {
         // If we need to apply variations, we can't use the cached mUnscaledFont
         // or mUnscaledFontBold here.
         // XXX todo: consider caching a small number of variation instances?
         RefPtr<IDWriteFontFace> fontFace;
         nsresult rv = CreateFontFace(getter_AddRefs(fontFace),
-                                     &aFontStyle->variationSettings,
+                                     aFontStyle,
                                      sims);
         if (NS_FAILED(rv)) {
             return nullptr;
         }
         RefPtr<UnscaledFontDWrite> unscaledFont =
             new UnscaledFontDWrite(fontFace, mIsSystemFont ? mFont : nullptr, sims);
         return new gfxDWriteFont(unscaledFont, this, aFontStyle, aNeedsBold);
     }
 
     ThreadSafeWeakPtr<UnscaledFontDWrite>& unscaledFontPtr =
         aNeedsBold ? mUnscaledFontBold : mUnscaledFont;
     RefPtr<UnscaledFontDWrite> unscaledFont(unscaledFontPtr);
     if (!unscaledFont) {
         RefPtr<IDWriteFontFace> fontFace;
-        nsresult rv = CreateFontFace(getter_AddRefs(fontFace), nullptr, sims);
+        nsresult rv = CreateFontFace(getter_AddRefs(fontFace), aFontStyle, sims);
         if (NS_FAILED(rv)) {
             return nullptr;
         }
         unscaledFont =
             new UnscaledFontDWrite(fontFace,
                                    mIsSystemFont ? mFont : nullptr, sims);
         unscaledFontPtr = unscaledFont;
     }
     return new gfxDWriteFont(unscaledFont, this, aFontStyle, aNeedsBold);
 }
 
 nsresult
 gfxDWriteFontEntry::CreateFontFace(IDWriteFontFace **aFontFace,
-                                   const nsTArray<gfxFontVariation>* aVariations,
+                                   const gfxFontStyle* aFontStyle,
                                    DWRITE_FONT_SIMULATIONS aSimulations)
 {
     // Convert an OpenType font tag from our uint32_t representation
     // (as constructed by TRUETYPE_TAG(...)) to the order DWrite wants.
     auto makeDWriteAxisTag = [](uint32_t aTag) {
         return DWRITE_MAKE_FONT_AXIS_TAG((aTag >> 24) & 0xff,
                                          (aTag >> 16) & 0xff,
                                          (aTag >> 8) & 0xff,
@@ -761,46 +767,40 @@ gfxDWriteFontEntry::CreateFontFace(IDWri
 
     // Do we need to modify DWrite simulations from what mFontFace has?
     bool needSimulations =
         (aSimulations & DWRITE_FONT_SIMULATIONS_BOLD) &&
         !(mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD);
 
     // If the IDWriteFontFace5 interface is available, we can go via
     // IDWriteFontResource to create a new modified face.
-    if (mFontFace5 && (aVariations && !aVariations->IsEmpty() ||
+    if (mFontFace5 && ((aFontStyle && !aFontStyle->variationSettings.IsEmpty()) ||
+                       !Weight().IsSingle() ||
                        needSimulations)) {
         RefPtr<IDWriteFontResource> resource;
         HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource));
         MOZ_ASSERT(SUCCEEDED(hr));
         AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> fontAxisValues;
-        if (aVariations) {
-            // Merge mVariationSettings and *aVariations if both present
-            const nsTArray<gfxFontVariation>* vars;
-            AutoTArray<gfxFontVariation,4> mergedSettings;
-            if (!aVariations) {
-                vars = &mVariationSettings;
-            } else  {
-                if (mVariationSettings.IsEmpty()) {
-                    vars = aVariations;
-                } else {
-                    gfxFontUtils::MergeVariations(mVariationSettings,
-                                                  *aVariations,
-                                                  &mergedSettings);
-                    vars = &mergedSettings;
-                }
-            }
-            for (const auto& v : *vars) {
+
+        // Get the variation settings needed to instantiate the fontEntry
+        // for a particular fontStyle.
+        AutoTArray<gfxFontVariation,4> vars;
+        GetVariationsForStyle(vars, *aFontStyle);
+
+        // Copy variation settings to DWrite's type.
+        if (!vars.IsEmpty()) {
+            for (const auto& v : vars) {
                 DWRITE_FONT_AXIS_VALUE axisValue = {
                     makeDWriteAxisTag(v.mTag),
                     v.mValue
                 };
                 fontAxisValues.AppendElement(axisValue);
             }
         }
+
         IDWriteFontFace5* ff5;
         resource->CreateFontFace(aSimulations,
                                  fontAxisValues.Elements(),
                                  fontAxisValues.Length(),
                                  &ff5);
         if (ff5) {
             *aFontFace = ff5;
         }
@@ -1146,23 +1146,26 @@ gfxDWriteFontList::InitFontListForPlatfo
             // add faces to Gill Sans MT
             for (i = 0; i < faces.Length(); i++) {
                 // change the entry's family name to match its adoptive family
                 faces[i]->mFamilyName = gillSansMTFamily->Name();
                 gillSansMTFamily->AddFontEntry(faces[i]);
 
                 if (LOG_FONTLIST_ENABLED()) {
                     gfxFontEntry *fe = faces[i];
+                    nsAutoCString weightString;
+                    fe->Weight().ToString(weightString);
                     LOG_FONTLIST(("(fontlist) moved (%s) to family (%s)"
-                         " with style: %s weight: %g stretch: %d",
+                         " with style: %s weight: %s stretch: %d",
                          NS_ConvertUTF16toUTF8(fe->Name()).get(),
                          NS_ConvertUTF16toUTF8(gillSansMTFamily->Name()).get(),
                          (fe->IsItalic()) ?
                           "italic" : (fe->IsOblique() ? "oblique" : "normal"),
-                         fe->Weight().ToFloat(), fe->Stretch()));
+                         weightString.get(),
+                         fe->Stretch()));
                 }
             }
 
             // remove Gills Sans
             mFontFamilies.Remove(nameGillSans);
         }
     }
 
--- a/gfx/thebes/gfxDWriteFontList.h
+++ b/gfx/thebes/gfxDWriteFontList.h
@@ -118,17 +118,17 @@ public:
         mStyle = (dwriteStyle == DWRITE_FONT_STYLE_ITALIC ?
                   FontSlantStyle::Italic() :
                   (dwriteStyle == DWRITE_FONT_STYLE_OBLIQUE ?
                    FontSlantStyle::Oblique() : FontSlantStyle::Normal()));
         mStretch = FontStretchFromDWriteStretch(aFont->GetStretch());
         int weight = NS_ROUNDUP(aFont->GetWeight() - 50, 100);
 
         weight = mozilla::Clamp(weight, 100, 900);
-        mWeight = FontWeight(weight);
+        mWeightRange = WeightRange(FontWeight(weight));
 
         mIsCJK = UNINITIALIZED_VALUE;
     }
 
     /**
      * Constructs a font entry using a font. But with custom font values.
      * This is used for creating correct font entries for @font-face with local
      * font source.
@@ -143,17 +143,17 @@ public:
                        IDWriteFont *aFont,
                        FontWeight aWeight,
                        FontStretch aStretch,
                        FontSlantStyle aStyle)
       : gfxFontEntry(aFaceName), mFont(aFont), mFontFile(nullptr),
         mIsSystemFont(false), mForceGDIClassic(false),
         mHasVariations(false), mHasVariationsInitialized(false)
     {
-        mWeight = aWeight;
+        mWeightRange = WeightRange(aWeight);
         mStretch = aStretch;
         mStyle = aStyle;
         mIsLocalUserFont = true;
         mIsCJK = UNINITIALIZED_VALUE;
     }
 
     /**
      * Constructs a font entry using a font file.
@@ -171,17 +171,17 @@ public:
                               FontWeight aWeight,
                               FontStretch aStretch,
                               FontSlantStyle aStyle)
       : gfxFontEntry(aFaceName), mFont(nullptr),
         mFontFile(aFontFile), mFontFileStream(aFontFileStream),
         mIsSystemFont(false), mForceGDIClassic(false),
         mHasVariations(false), mHasVariationsInitialized(false)
     {
-        mWeight = aWeight;
+        mWeightRange = WeightRange(aWeight);
         mStretch = aStretch;
         mStyle = aStyle;
         mIsDataUserFont = true;
         mIsCJK = UNINITIALIZED_VALUE;
     }
 
     gfxFontEntry* Clone() const override;
 
@@ -212,17 +212,17 @@ protected:
     virtual nsresult CopyFontTable(uint32_t aTableTag,
                                    nsTArray<uint8_t>& aBuffer) override;
 
     virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle,
                                         bool aNeedsBold);
     
     nsresult CreateFontFace(
         IDWriteFontFace **aFontFace,
-        const nsTArray<gfxFontVariation>* aVariations = nullptr,
+        const gfxFontStyle* aFontStyle = nullptr,
         DWRITE_FONT_SIMULATIONS aSimulations = DWRITE_FONT_SIMULATIONS_NONE);
 
     static bool InitLogFont(IDWriteFont *aFont, LOGFONTW *aLogFont);
 
     /**
      * A fontentry only needs to have either of these. If it has both only
      * the IDWriteFont will be used.
      */
--- a/gfx/thebes/gfxDWriteFonts.cpp
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -152,17 +152,18 @@ gfxDWriteFont::GetFakeMetricsForArialBla
 
     return true;
 }
 
 void
 gfxDWriteFont::ComputeMetrics(AntialiasOption anAAOption)
 {
     DWRITE_FONT_METRICS fontMetrics;
-    if (!(mFontEntry->Weight() == FontWeight(900) &&
+    if (!(mFontEntry->Weight().Min() == FontWeight(900) &&
+          mFontEntry->Weight().Max() == FontWeight(900) &&
           !mFontEntry->IsUserFont() &&
           mFontEntry->Name().EqualsLiteral("Arial Black") &&
           GetFakeMetricsForArialBlack(&fontMetrics)))
     {
         mFontFace->GetMetrics(&fontMetrics);
     }
 
     if (mStyle.sizeAdjust >= 0.0) {
--- a/gfx/thebes/gfxFT2FontBase.cpp
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -225,32 +225,23 @@ gfxFT2FontBase::InitMetrics()
         mMetrics.strikeoutOffset = 0.25 * emHeight;
         mMetrics.strikeoutSize = underlineSize;
 
         SanitizeMetrics(&mMetrics, false);
         return;
     }
 
     if ((!mFontEntry->mVariationSettings.IsEmpty() ||
-         !mStyle.variationSettings.IsEmpty()) &&
+         !mStyle.variationSettings.IsEmpty() ||
+         !mFontEntry->Weight().IsSingle()) &&
          (face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) {
         // Resolve variations from entry (descriptor) and style (property)
-        const nsTArray<gfxFontVariation>* settings;
-        AutoTArray<gfxFontVariation,8> mergedSettings;
-        if (mFontEntry->mVariationSettings.IsEmpty()) {
-            settings = &mStyle.variationSettings;
-        } else if (mStyle.variationSettings.IsEmpty()) {
-            settings = &mFontEntry->mVariationSettings;
-        } else {
-            gfxFontUtils::MergeVariations(mFontEntry->mVariationSettings,
-                                          mStyle.variationSettings,
-                                          &mergedSettings);
-            settings = &mergedSettings;
-        }
-        SetupVarCoords(face, *settings, &mCoords);
+        AutoTArray<gfxFontVariation,8> settings;
+        mFontEntry->GetVariationsForStyle(settings, mStyle);
+        SetupVarCoords(face, settings, &mCoords);
         if (!mCoords.IsEmpty()) {
 #if MOZ_TREE_FREETYPE
             FT_Set_Var_Design_Coordinates(face, mCoords.Length(), mCoords.Elements());
 #else
             typedef FT_Error (*SetCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*);
             static SetCoordsFunc setCoords;
             static bool firstTime = true;
             if (firstTime) {
--- a/gfx/thebes/gfxFT2FontList.cpp
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -219,17 +219,17 @@ FT2FontEntry::~FT2FontEntry()
 
 gfxFontEntry*
 FT2FontEntry::Clone() const
 {
     MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
     FT2FontEntry* fe = new FT2FontEntry(Name());
     fe->mFilename = mFilename;
     fe->mFTFontIndex = mFTFontIndex;
-    fe->mWeight = mWeight;
+    fe->mWeightRange = mWeightRange;
     fe->mStretch = mStretch;
     fe->mStyle = mStyle;
     return fe;
 }
 
 gfxFont*
 FT2FontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
 {
@@ -278,17 +278,17 @@ FT2FontEntry::CreateFontEntry(const nsAS
     }
     // Create our FT2FontEntry, which inherits the name of the userfont entry
     // as it's not guaranteed that the face has valid names (bug 737315)
     FT2FontEntry* fe =
         FT2FontEntry::CreateFontEntry(face, nullptr, 0, aFontName,
                                       aFontData, aLength);
     if (fe) {
         fe->mStyle = aStyle;
-        fe->mWeight = aWeight;
+        fe->mWeightRange = WeightRange(aWeight);
         fe->mStretch = aStretch;
         fe->mIsDataUserFont = true;
     }
     return fe;
 }
 
 class FTUserFontData {
 public:
@@ -325,17 +325,18 @@ FTFontDestroyFunc(void *data)
 FT2FontEntry*
 FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE)
 {
     FT2FontEntry *fe = new FT2FontEntry(aFLE.faceName());
     fe->mFilename = aFLE.filepath();
     fe->mFTFontIndex = aFLE.index();
     // The weight transported across IPC is a float, so we need to explicitly
     // convert it back to a FontWeight.
-    fe->mWeight = FontWeight(aFLE.weight());
+    fe->mWeightRange = WeightRange(FontWeight(aFLE.minWeight()),
+                                   FontWeight(aFLE.maxWeight()));
     fe->mStretch = FontStretch(float(aFLE.stretch()));
     fe->mStyle = aFLE.italic()
       ? FontSlantStyle::Italic() : FontSlantStyle::Normal();
     return fe;
 }
 
 // Helpers to extract font entry properties from an FT_Face
 static bool
@@ -388,17 +389,17 @@ FT2FontEntry::CreateFontEntry(FT_Face aF
                               const char* aFilename, uint8_t aIndex,
                               const nsAString& aName,
                               const uint8_t* aFontData,
                               uint32_t aLength)
 {
     FT2FontEntry *fe = new FT2FontEntry(aName);
     fe->mStyle = (FTFaceIsItalic(aFace) ?
                   FontSlantStyle::Italic() : FontSlantStyle::Normal());
-    fe->mWeight = FTFaceGetWeight(aFace);
+    fe->mWeightRange = WeightRange(FTFaceGetWeight(aFace));
     fe->mFilename = aFilename;
     fe->mFTFontIndex = aIndex;
 
     if (aFontData) {
         fe->mFTFace = aFace;
         int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
                     FT_LOAD_DEFAULT :
                     (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
@@ -455,38 +456,27 @@ FT2FontEntry::CairoFontFace(const gfxFon
                                       userFontData, FTFontDestroyFunc);
         mFTFace = face.forget();
     }
 
     // If variations are present, we will not use our cached mFontFace
     // but always create a new cairo_font_face_t because its FT_Face will
     // have custom variation coordinates applied.
     if ((!mVariationSettings.IsEmpty() ||
-        (aStyle && !aStyle->variationSettings.IsEmpty())) &&
+        (aStyle && !aStyle->variationSettings.IsEmpty()) ||
+        !Weight().IsSingle()) &&
         (mFTFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) {
         int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
                     FT_LOAD_DEFAULT :
                     (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
         // Resolve variations from entry (descriptor) and style (property)
-        const nsTArray<gfxFontVariation>* settings;
-        AutoTArray<gfxFontVariation,8> mergedSettings;
-        if (aStyle) {
-            if (mVariationSettings.IsEmpty()) {
-                settings = &aStyle->variationSettings;
-            } else {
-                gfxFontUtils::MergeVariations(mVariationSettings,
-                                              aStyle->variationSettings,
-                                              &mergedSettings);
-                settings = &mergedSettings;
-            }
-        } else {
-            settings = &mVariationSettings;
-        }
+        AutoTArray<gfxFontVariation,8> settings;
+        GetVariationsForStyle(settings, aStyle ? *aStyle : gfxFontStyle());
         AutoTArray<FT_Fixed,8> coords;
-        gfxFT2FontBase::SetupVarCoords(mFTFace, *settings, &coords);
+        gfxFT2FontBase::SetupVarCoords(mFTFace, settings, &coords);
         // Create a separate FT_Face because we need to apply custom
         // variation settings to it.
         FT_Face ftFace;
         if (!mFilename.IsEmpty()) {
             ftFace = Factory::NewFTFace(nullptr, mFilename.get(), mFTFontIndex);
         } else {
             auto ufd = reinterpret_cast<FTUserFontData*>(
                 cairo_font_face_get_user_data(mFontFace, &sFTUserFontDataKey));
@@ -665,17 +655,18 @@ FT2FontFamily::AddFacesToFontList(Infall
             continue;
         }
 
         // We convert the weight to a float purely for transport across IPC.
         // Ideally we'd avoid doing that.
         aFontList->AppendElement(FontListEntry(Name(),
                                                fe->Name(),
                                                fe->mFilename,
-                                               fe->Weight().ToFloat(),
+                                               fe->Weight().Min().ToFloat(),
+                                               fe->Weight().Max().ToFloat(),
                                                fe->Stretch().Percentage(),
                                                fe->mStyle.IsItalic()
                                                 ? NS_FONT_STYLE_ITALIC
                                                 : NS_FONT_STYLE_NORMAL,
                                                fe->mFTFontIndex));
     }
 }
 
@@ -962,25 +953,33 @@ gfxFT2FontList::AppendFacesFromCachedFac
         if (!(end = strchr(beginning, ','))) {
             break;
         }
         bool italic = (*beginning != '0');
         beginning = end + 1;
         if (!(end = strchr(beginning, ','))) {
             break;
         }
-        uint32_t weight = strtoul(beginning, nullptr, 10);
+        char* limit;
+        float minWeight = strtof(beginning, &limit);
+        float maxWeight;
+        if (*limit == ':' && limit + 1 < end) {
+            maxWeight = strtof(limit + 1, nullptr);
+        } else {
+            maxWeight = minWeight;
+        }
         beginning = end + 1;
         if (!(end = strchr(beginning, ','))) {
             break;
         }
         uint32_t stretch = strtoul(beginning, nullptr, 10);
 
         FontListEntry fle(familyName, faceName, aFileName,
-                          weight, stretch, italic, index);
+                          minWeight, maxWeight,
+                          stretch, italic, index);
         AppendFaceFromFontListEntry(fle, aStdFile);
 
         beginning = end + 1;
         end = strchr(beginning, ',');
     }
 }
 
 static void
@@ -990,17 +989,19 @@ AppendToFaceList(nsCString& aFaceList,
     aFaceList.Append(NS_ConvertUTF16toUTF8(aFamilyName));
     aFaceList.Append(',');
     aFaceList.Append(NS_ConvertUTF16toUTF8(aFontEntry->Name()));
     aFaceList.Append(',');
     aFaceList.AppendInt(aFontEntry->mFTFontIndex);
     aFaceList.Append(',');
     aFaceList.Append(aFontEntry->IsItalic() ? '1' : '0');
     aFaceList.Append(',');
-    aFaceList.AppendFloat(aFontEntry->Weight().ToFloat());
+    aFaceList.AppendFloat(aFontEntry->Weight().Min().ToFloat());
+    aFaceList.Append(':');
+    aFaceList.AppendFloat(aFontEntry->Weight().Max().ToFloat());
     aFaceList.Append(',');
     // FIXME(emilio): Probably the stretch should be converted to float.
     aFaceList.AppendInt(int32_t(aFontEntry->Stretch().Percentage()));
     aFaceList.Append(',');
 }
 
 void
 FT2FontEntry::CheckForBrokenFont(gfxFontFamily *aFamily)
@@ -1149,22 +1150,25 @@ gfxFT2FontList::AddFaceToList(const nsCS
         }
         fe->mStandardFace = (aStdFile == kStandard);
         family->AddFontEntry(fe);
 
         fe->CheckForBrokenFont(family);
 
         AppendToFaceList(aFaceList, name, fe);
         if (LOG_ENABLED()) {
+            nsAutoCString weightString;
+            fe->Weight().ToString(weightString);
             LOG(("(fontinit) added (%s) to family (%s)"
-                 " with style: %s weight: %g stretch: %g%%",
+                 " with style: %s weight: %s stretch: %g%%",
                  NS_ConvertUTF16toUTF8(fe->Name()).get(),
                  NS_ConvertUTF16toUTF8(family->Name()).get(),
                  fe->IsItalic() ? "italic" : "normal",
-                 fe->Weight().ToFloat(), fe->Stretch().Percentage()));
+                 weightString.get(),
+                 fe->Stretch().Percentage()));
         }
     }
 }
 
 void
 gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive,
                                             const nsCString& aEntryName,
                                             FontNameCache *aCache,
@@ -1522,17 +1526,17 @@ searchDone:
 
     FT2FontEntry* fe =
         FT2FontEntry::CreateFontEntry(fontEntry->mFTFace,
                                       fontEntry->mFilename.get(),
                                       fontEntry->mFTFontIndex,
                                       fontEntry->Name(), nullptr);
     if (fe) {
         fe->mStyle = aStyle;
-        fe->mWeight = aWeight;
+        fe->mWeightRange = WeightRange(aWeight);
         fe->mStretch = aStretch;
         fe->mIsLocalUserFont = true;
     }
 
     return fe;
 }
 
 gfxFontFamily*
--- a/gfx/thebes/gfxFcPlatformFontList.cpp
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -250,17 +250,17 @@ gfxFontconfigFontEntry::gfxFontconfigFon
         mStyle = FontSlantStyle::Italic();
     }
 
     // weight
     int weight;
     if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) != FcResultMatch) {
         weight = FC_WEIGHT_REGULAR;
     }
-    mWeight = MapFcWeight(weight);
+    mWeightRange = WeightRange(MapFcWeight(weight));
 
     // width
     int width;
     if (FcPatternGetInteger(aFontPattern, FC_WIDTH, 0, &width) != FcResultMatch) {
         width = FC_WIDTH_NORMAL;
     }
     mStretch = MapFcWidth(width);
 }
@@ -323,17 +323,17 @@ gfxFontconfigFontEntry::gfxFontconfigFon
                                                uint32_t aLength,
                                                FT_Face aFace)
     : gfxFontEntry(aFaceName),
       mFTFace(aFace), mFTFaceInitialized(true),
       mIgnoreFcCharmap(true),
       mHasVariationsInitialized(false),
       mAspect(0.0), mFontData(aData), mLength(aLength)
 {
-    mWeight = aWeight;
+    mWeightRange = WeightRange(aWeight);
     mStyle = aStyle;
     mStretch = aStretch;
     mIsDataUserFont = true;
 
     mFontPattern = CreatePatternForFace(mFTFace);
 
     mUserFontData = new FTUserFontData(mFTFace, mFontData);
 }
@@ -343,17 +343,17 @@ gfxFontconfigFontEntry::gfxFontconfigFon
                                                FontWeight aWeight,
                                                FontStretch aStretch,
                                                FontSlantStyle aStyle)
         : gfxFontEntry(aFaceName), mFontPattern(aFontPattern),
           mFTFace(nullptr), mFTFaceInitialized(false),
           mHasVariationsInitialized(false),
           mAspect(0.0), mFontData(nullptr), mLength(0)
 {
-    mWeight = aWeight;
+    mWeightRange = WeightRange(aWeight);
     mStyle = aStyle;
     mStretch = aStretch;
     mIsLocalUserFont = true;
 
     // The proper setting of mIgnoreFcCharmap is tricky for fonts loaded
     // via src:local()...
     // If the local font happens to come from the application fontset,
     // we want to set it to true so that color/svg fonts will work even
@@ -766,32 +766,24 @@ gfxFontconfigFontEntry::CreateScaledFont
 
     if (needsOblique) {
         // disable embedded bitmaps (mimics behavior in 90-synthetic.conf)
         FcPatternDel(aRenderPattern, FC_EMBEDDED_BITMAP);
         FcPatternAddBool(aRenderPattern, FC_EMBEDDED_BITMAP, FcFalse);
     }
 
     AutoTArray<FT_Fixed,8> coords;
-    if (!aStyle->variationSettings.IsEmpty() || !mVariationSettings.IsEmpty()) {
+    if (!aStyle->variationSettings.IsEmpty() ||
+        !mVariationSettings.IsEmpty() ||
+        !Weight().IsSingle()) {
         FT_Face ftFace = GetFTFace();
         if (ftFace) {
-            const nsTArray<gfxFontVariation>* settings;
-            AutoTArray<gfxFontVariation,8> mergedSettings;
-            if (mVariationSettings.IsEmpty()) {
-                settings = &aStyle->variationSettings;
-            } else if (aStyle->variationSettings.IsEmpty()) {
-                settings = &mVariationSettings;
-            } else {
-                gfxFontUtils::MergeVariations(mVariationSettings,
-                                              aStyle->variationSettings,
-                                              &mergedSettings);
-                settings = &mergedSettings;
-            }
-            gfxFT2FontBase::SetupVarCoords(ftFace, *settings, &coords);
+            AutoTArray<gfxFontVariation,8> settings;
+            GetVariationsForStyle(settings, *aStyle);
+            gfxFT2FontBase::SetupVarCoords(ftFace, settings, &coords);
         }
     }
 
     cairo_font_face_t *face =
         cairo_ft_font_face_create_for_pattern(aRenderPattern,
                                               coords.Elements(),
                                               coords.Length());
 
@@ -1213,31 +1205,35 @@ gfxFontconfigFontFamily::FindStyleVariat
 
         // figure out the psname/fullname and choose which to use as the facename
         nsAutoString psname, fullname;
         GetFaceNames(face, mName, psname, fullname);
         const nsAutoString& faceName = !psname.IsEmpty() ? psname : fullname;
 
         gfxFontconfigFontEntry *fontEntry =
             new gfxFontconfigFontEntry(faceName, face, mContainsAppFonts);
+        fontEntry->SetupVariationRanges();
+
         AddFontEntry(fontEntry);
 
         if (fontEntry->IsNormalStyle()) {
             numRegularFaces++;
         }
 
         if (LOG_FONTLIST_ENABLED()) {
+            nsAutoCString weightString;
+            fontEntry->Weight().ToString(weightString);
             LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
-                 " with style: %s weight: %g stretch: %g%%"
+                 " with style: %s weight: %s stretch: %g%%"
                  " psname: %s fullname: %s",
                  NS_ConvertUTF16toUTF8(fontEntry->Name()).get(),
                  NS_ConvertUTF16toUTF8(Name()).get(),
                  (fontEntry->IsItalic()) ?
                   "italic" : (fontEntry->IsOblique() ? "oblique" : "normal"),
-                 fontEntry->Weight().ToFloat(),
+                 weightString.get(),
                  fontEntry->Stretch().Percentage(),
                  NS_ConvertUTF16toUTF8(psname).get(),
                  NS_ConvertUTF16toUTF8(fullname).get()));
         }
     }
 
     // somewhat arbitrary, but define a family with two or more regular
     // faces as a family for which intra-family fallback should be used
@@ -1333,17 +1329,18 @@ gfxFontconfigFontFamily::FindAllFontsFor
             static_cast<gfxFontconfigFontEntry*>(aFontEntryList[i]);
         double dist = SizeDistance(entry, aFontStyle,
                                    mForceScalable || aIgnoreSizeTolerance);
         // If the entry is scalable or has a style that does not match
         // the group of unscalable fonts, then start a new group.
         if (dist < 0.0 ||
             !bestEntry ||
             bestEntry->Stretch() != entry->Stretch() ||
-            bestEntry->Weight() != entry->Weight() ||
+            bestEntry->Weight().Min() != entry->Weight().Min() ||
+            bestEntry->Weight().Max() != entry->Weight().Max() ||
             bestEntry->mStyle != entry->mStyle) {
             // If the best entry in this group is still outside the tolerance,
             // then skip the entire group.
             if (bestDist >= kRejectDistance) {
                 skipped++;
             }
             // Remove any compacted entries from the previous group.
             if (skipped) {
--- a/gfx/thebes/gfxFontEntry.cpp
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -75,17 +75,17 @@ gfxFontEntry::gfxFontEntry() :
     mGraphiteSpaceContextualsInitialized(false),
     mHasGraphiteSpaceContextuals(false),
     mSpaceGlyphIsInvisible(false),
     mSpaceGlyphIsInvisibleInitialized(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
     mCheckedForColorGlyph(false),
-    mWeight(500),
+    mWeightRange(FontWeight(500)),
     mStretch(FontStretch::Normal()),
     mStyle(FontSlantStyle::Normal()),
     mUVSOffset(0), mUVSData(nullptr),
     mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     mCOLR(nullptr),
     mCPAL(nullptr),
     mUnitsPerEm(0),
     mHBFace(nullptr),
@@ -114,17 +114,17 @@ gfxFontEntry::gfxFontEntry(const nsAStri
     mGraphiteSpaceContextualsInitialized(false),
     mHasGraphiteSpaceContextuals(false),
     mSpaceGlyphIsInvisible(false),
     mSpaceGlyphIsInvisibleInitialized(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
     mCheckedForColorGlyph(false),
-    mWeight(500),
+    mWeightRange(FontWeight(500)),
     mStretch(FontStretch::Normal()),
     mStyle(FontSlantStyle::Normal()),
     mUVSOffset(0), mUVSData(nullptr),
     mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     mCOLR(nullptr),
     mCPAL(nullptr),
     mUnitsPerEm(0),
     mHBFace(nullptr),
@@ -1027,16 +1027,96 @@ gfxFontEntry::GetColorLayersInfo(uint32_
     return gfxFontUtils::GetColorGlyphLayers(mCOLR,
                                              mCPAL,
                                              aGlyphId,
                                              aDefaultColor,
                                              aLayerGlyphs,
                                              aLayerColors);
 }
 
+void
+gfxFontEntry::SetupVariationRanges()
+{
+    if (!HasVariations() || IsUserFont()) {
+        return;
+    }
+    AutoTArray<gfxFontVariationAxis,4> axes;
+    GetVariationAxes(axes);
+    for (const auto& axis : axes) {
+        switch (axis.mTag) {
+        case HB_TAG('w','g','h','t'):
+            // If the axis range looks like it doesn't fit the CSS font-weight
+            // scale, we don't hook up the high-level property. Setting 'wght'
+            // with font-variation-settings will still work.
+            // Strictly speaking, the min value should be checked against 1.0,
+            // not 0.0, but we'll allow font makers that amount of leeway, as
+            // in practice a number of fonts seem to use 0..1000.
+            if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0 &&
+                // If axis.mMaxValue is less than the default weight we already
+                // set up, assume the axis has a non-standard range (like Skia)
+                // and don't try to map it.
+                Weight().Min() <= FontWeight(axis.mMaxValue)) {
+                mStandardFace = FontWeight(axis.mDefaultValue) == Weight().Min();
+                mWeightRange =
+                    WeightRange(FontWeight(std::max(1.0f, axis.mMinValue)),
+                                FontWeight(axis.mMaxValue));
+            }
+            break;
+        // XXX todo:
+        // case HB_TAG('w','d','t','h'):
+        // case HB_TAG('s','l','n','t'):
+        // case HB_TAG('i','t','a','l'):
+        default:
+            continue;
+        }
+    }
+}
+
+void
+gfxFontEntry::GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult,
+                                    const gfxFontStyle& aStyle)
+{
+    // Resolve high-level CSS properties from the requested style
+    // (font-{style,weight,stretch}) to the appropriate variations.
+    if (!Weight().IsSingle()) {
+        float clampedWeight = Weight().Clamp(aStyle.weight).ToFloat();
+        aResult.AppendElement(gfxFontVariation{HB_TAG('w','g','h','t'),
+                                               clampedWeight});
+    }
+
+    // XXX todo: 'wdth', 'slnt', 'ital'
+
+    auto replaceOrAppend = [&aResult](const gfxFontVariation& aSetting) {
+        struct TagEquals {
+            bool Equals(const gfxFontVariation& aIter, uint32_t aTag) const {
+                return aIter.mTag == aTag;
+            }
+        };
+        auto index = aResult.IndexOf(aSetting.mTag, 0, TagEquals());
+        if (index == aResult.NoIndex) {
+            aResult.AppendElement(aSetting);
+        } else {
+            aResult[index].mValue = aSetting.mValue;
+        }
+    };
+
+    // The low-level font-variation-settings descriptor from @font-face,
+    // if present, takes precedence over automatic variation settings
+    // from high-level properties.
+    for (const auto& v : mVariationSettings) {
+        replaceOrAppend(v);
+    }
+
+    // And the low-level font-variation-settings property takes precedence
+    // over the descriptor.
+    for (const auto& v : aStyle.variationSettings) {
+        replaceOrAppend(v);
+    }
+}
+
 size_t
 gfxFontEntry::FontTableHashEntry::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
     size_t n = 0;
     if (mBlob) {
         n += aMallocSizeOf(mBlob);
     }
     if (mSharedBlobData) {
@@ -1235,43 +1315,45 @@ StretchDistance(FontStretch aFontStretch
 // weights are farther away than lighter weights. If the target is 5 and the
 // font weight 995, the distance would be 1590 for the same reason.
 
 #define REVERSE_WEIGHT_DISTANCE 600
 #define WEIGHT_SHIFT             11 // number of bits to contain weight distance
 
 // weight distance ==> [0,1598]
 static inline uint32_t
-WeightDistance(FontWeight aFontWeight, FontWeight aTargetWeight)
+WeightDistance(const gfxFontEntry* aFontEntry, FontWeight aTargetWeight)
 {
     // Compute a measure of the "distance" between the requested
     // weight and the given fontEntry
 
     float distance = 0.0f, addedDistance = 0.0f;
-    if (aTargetWeight != aFontWeight) {
+    FontWeight minWeight = aFontEntry->Weight().Min();
+    FontWeight maxWeight = aFontEntry->Weight().Max();
+    if (aTargetWeight < minWeight || aTargetWeight > maxWeight) {
         if (aTargetWeight > FontWeight(500)) {
-            distance = aFontWeight - aTargetWeight;
+            distance = minWeight - aTargetWeight;
         } else if (aTargetWeight < FontWeight(400)) {
-            distance = aTargetWeight - aFontWeight;
+            distance = aTargetWeight - maxWeight;
         } else {
             // special case - target is between 400 and 500
 
             // font weights between 400 and 500 are close
-            if (aFontWeight >= FontWeight(400) &&
-                aFontWeight <= FontWeight(500)) {
-                if (aFontWeight < aTargetWeight) {
-                    distance = FontWeight(500) - aFontWeight;
+            if (maxWeight >= FontWeight(400) &&
+                minWeight <= FontWeight(500)) {
+                if (maxWeight < aTargetWeight) {
+                    distance = FontWeight(500) - maxWeight;
                 } else {
-                    distance = aFontWeight - aTargetWeight;
+                    distance = minWeight - aTargetWeight;
                 }
             } else {
                 // font weights outside use rule for target weights < 400 with
                 // added distance to separate from font weights in
                 // the [400..500] range
-                distance = aTargetWeight - aFontWeight;
+                distance = aTargetWeight - maxWeight;
                 addedDistance = 100;
             }
         }
         if (distance < 0.0f) {
             distance = -distance + REVERSE_WEIGHT_DISTANCE;
         }
         distance += addedDistance;
     }
@@ -1283,18 +1365,17 @@ WeightDistance(FontWeight aFontWeight, F
 static inline uint32_t
 WeightStyleStretchDistance(gfxFontEntry* aFontEntry,
                            const gfxFontStyle& aTargetStyle)
 {
     // weight/style/stretch priority: stretch >> style >> weight
     uint32_t stretchDist =
         StretchDistance(aFontEntry->mStretch, aTargetStyle.stretch);
     uint32_t styleDist = StyleDistance(aFontEntry->mStyle, aTargetStyle.style);
-    uint32_t weightDist =
-        WeightDistance(aFontEntry->Weight(), aTargetStyle.weight);
+    uint32_t weightDist = WeightDistance(aFontEntry, aTargetStyle.weight);
 
     NS_ASSERTION(weightDist < (1 << WEIGHT_SHIFT), "weight value out of bounds");
     NS_ASSERTION(styleDist < (1 << STYLE_SHIFT), "slope value out of bounds");
 
     return (stretchDist << (STYLE_SHIFT + WEIGHT_SHIFT)) |
            (styleDist << WEIGHT_SHIFT) |
            weightDist;
 }
@@ -1443,18 +1524,21 @@ gfxFontFamily::CheckForSimpleFamily()
 
     gfxFontEntry *faces[4] = { 0 };
     for (uint8_t i = 0; i < count; ++i) {
         gfxFontEntry *fe = mAvailableFonts[i];
         if (fe->Stretch() != firstStretch || fe->IsOblique()) {
             // simple families don't have varying font-stretch or oblique
             return;
         }
+        if (fe->Weight().Min() != fe->Weight().Max()) {
+            return; // family with variation fonts is not considered "simple"
+        }
         uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) |
-                            (fe->Weight() >= FontWeight(600) ? kBoldMask : 0);
+                            (fe->IsBold() ? kBoldMask : 0);
         if (faces[faceIndex]) {
             return; // two faces resolve to the same slot; family isn't "simple"
         }
         faces[faceIndex] = fe;
     }
 
     // we have successfully slotted the available faces into the standard
     // 4-face framework
@@ -1491,43 +1575,51 @@ gfxFontFamily::ContainsFace(gfxFontEntry
 
 void gfxFontFamily::LocalizedName(nsAString& aLocalizedName)
 {
     // just return the primary name; subclasses should override
     aLocalizedName = mName;
 }
 
 // metric for how close a given font matches a style
-static int32_t
+static float
 CalcStyleMatch(gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle)
 {
-    int32_t rank = 0;
+    float rank = 0;
     if (aStyle) {
-         // italics
-         bool wantUpright = aStyle->style.IsNormal();
-         if (aFontEntry->IsUpright() == wantUpright) {
-             rank += 10;
-         }
+        // TODO: stretch
+
+        // italics
+        bool wantUpright = aStyle->style.IsNormal();
+        if (aFontEntry->IsUpright() == wantUpright) {
+            rank += 5000.0f;
+        }
 
         // measure of closeness of weight to the desired value
-        rank += 9 - Abs((aFontEntry->Weight() - aStyle->weight) / 100.0f);
+        if (aFontEntry->Weight().Min() > aStyle->weight) {
+            rank += aFontEntry->Weight().Min() - aStyle->weight;
+        } else if (aFontEntry->Weight().Max() < aStyle->weight) {
+            rank += aStyle->weight - aFontEntry->Weight().Max();
+        } else {
+            rank += 2000.0f; // the font supports the exact weight wanted
+        }
     } else {
         // if no font to match, prefer non-bold, non-italic fonts
         if (aFontEntry->IsUpright()) {
-            rank += 3;
+            rank += 2000.0f;
         }
         if (!aFontEntry->IsBold()) {
-            rank += 2;
+            rank += 1000.0f;
         }
     }
 
     return rank;
 }
 
-#define RANK_MATCHED_CMAP   20
+#define RANK_MATCHED_CMAP   10000.0f
 
 void
 gfxFontFamily::FindFontForChar(GlobalFontMatch *aMatchData)
 {
     if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) {
         // none of the faces in the family support the required char,
         // so bail out immediately
         return;
@@ -1535,17 +1627,17 @@ gfxFontFamily::FindFontForChar(GlobalFon
 
     bool needsBold;
     gfxFontEntry *fe =
         FindFontForStyle(aMatchData->mStyle ? *aMatchData->mStyle
                                             : gfxFontStyle(),
                          needsBold, true);
 
     if (fe && !fe->SkipDuringSystemFallback()) {
-        int32_t rank = 0;
+        float rank = 0;
 
         if (fe->HasCharacter(aMatchData->mCh)) {
             rank += RANK_MATCHED_CMAP;
             aMatchData->mCount++;
 
             LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun);
 
             if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) {
@@ -1595,17 +1687,17 @@ gfxFontFamily::FindFontForChar(GlobalFon
 
 void
 gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch *aMatchData)
 {
     uint32_t i, numFonts = mAvailableFonts.Length();
     for (i = 0; i < numFonts; i++) {
         gfxFontEntry *fe = mAvailableFonts[i];
         if (fe && fe->HasCharacter(aMatchData->mCh)) {
-            int32_t rank = RANK_MATCHED_CMAP;
+            float rank = RANK_MATCHED_CMAP;
             rank += CalcStyleMatch(fe, aMatchData->mStyle);
             if (rank > aMatchData->mMatchRank
                 || (rank == aMatchData->mMatchRank &&
                     Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0))
             {
                 aMatchData->mBestMatch = fe;
                 aMatchData->mMatchedFamily = this;
                 aMatchData->mMatchRank = rank;
--- a/gfx/thebes/gfxFontEntry.h
+++ b/gfx/thebes/gfxFontEntry.h
@@ -110,16 +110,17 @@ struct gfxFontFeatureInfo {
 
 class gfxFontEntry {
 public:
     typedef mozilla::gfx::DrawTarget DrawTarget;
     typedef mozilla::unicode::Script Script;
     typedef mozilla::FontWeight FontWeight;
     typedef mozilla::FontSlantStyle FontSlantStyle;
     typedef mozilla::FontStretch FontStretch;
+    typedef mozilla::WeightRange WeightRange;
 
     // Used by stylo
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontEntry)
 
     explicit gfxFontEntry(const nsAString& aName, bool aIsStandardFace = false);
 
     // Create a new entry that refers to the same font as this, but without
     // additional state that may have been set up (such as family name).
@@ -138,39 +139,40 @@ public:
     // will (usually, except on Linux) load and parse the 'name' table;
     // they are intended only for the font-inspection API, not for
     // perf-critical layout/drawing work.
 
     // The "real" name of the face, if available from the font resource;
     // returns Name() if nothing better is available.
     virtual nsString RealFaceName();
 
-    FontWeight Weight() const { return mWeight; }
+    WeightRange Weight() const { return mWeightRange; }
     FontStretch Stretch() const { return mStretch; }
 
     bool IsUserFont() const { return mIsDataUserFont || mIsLocalUserFont; }
     bool IsLocalUserFont() const { return mIsLocalUserFont; }
     bool IsFixedPitch() const { return mFixedPitch; }
     bool IsItalic() const { return mStyle.IsItalic(); }
     bool IsOblique() const { return mStyle.IsOblique(); }
     bool IsUpright() const { return mStyle.IsNormal(); }
-    bool IsBold() const { return mWeight.IsBold(); } // bold == weights 600 and above
+    bool IsBold() const { return Weight().Max().IsBold(); } // bold == weights 600 and above
     bool IgnoreGDEF() const { return mIgnoreGDEF; }
     bool IgnoreGSUB() const { return mIgnoreGSUB; }
 
     // Return whether the face corresponds to "normal" CSS style properties:
     //    font-style: normal;
     //    font-weight: normal;
     //    font-stretch: normal;
     // If this is false, we might want to fall back to a different face and
     // possibly apply synthetic styling.
     bool IsNormalStyle() const
     {
         return IsUpright() &&
-               Weight() == FontWeight::Normal() &&
+               Weight().Min() <= FontWeight::Normal() &&
+               Weight().Max() >= FontWeight::Normal() &&
                Stretch().IsNormal();
     }
 
     // whether a feature is supported by the font (limited to a small set
     // of features for which some form of fallback needs to be implemented)
     virtual bool SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag);
     bool SupportsGraphiteFeature(uint32_t aFeatureTag);
 
@@ -365,16 +367,26 @@ public:
     }
     virtual void GetVariationAxes(nsTArray<gfxFontVariationAxis>& aVariationAxes)
     {
     }
     virtual void GetVariationInstances(nsTArray<gfxFontVariationInstance>& aInstances)
     {
     }
 
+    // Set up the entry's weight/stretch/style ranges according to axes found
+    // by GetVariationAxes (for installed fonts; do NOT call this for user
+    // fonts, where the ranges are provided by @font-face descriptors).
+    void SetupVariationRanges();
+
+    // Get variation axis settings that should be used to implement a particular
+    // font style using this resource.
+    void GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult,
+                               const gfxFontStyle& aStyle);
+
     // Get the font's list of features (if any) for DevTools support.
     void GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo);
 
     nsString         mName;
     nsString         mFamilyName;
 
     bool             mFixedPitch  : 1;
     bool             mIsBadUnderlineFont : 1;
@@ -400,19 +412,19 @@ public:
     bool             mGrFaceInitialized : 1;
     bool             mCheckedForColorGlyph : 1;
 
     // bitvector of substitution space features per script, one each
     // for default and non-default features
     uint32_t         mDefaultSubSpaceFeatures[(int(Script::NUM_SCRIPT_CODES) + 31) / 32];
     uint32_t         mNonDefaultSubSpaceFeatures[(int(Script::NUM_SCRIPT_CODES) + 31) / 32];
 
-    FontWeight mWeight;
-    FontStretch mStretch;
-    FontSlantStyle mStyle;
+    WeightRange      mWeightRange;
+    FontStretch      mStretch;
+    FontSlantStyle   mStyle;
 
     RefPtr<gfxCharacterMap> mCharacterMap;
     uint32_t         mUVSOffset;
     mozilla::UniquePtr<uint8_t[]> mUVSData;
     mozilla::UniquePtr<gfxUserFontData> mUserFontData;
     mozilla::UniquePtr<gfxSVGGlyphs> mSVGGlyphs;
     // list of gfxFonts that are using SVG glyphs
     nsTArray<gfxFont*> mFontsUsingSVGGlyphs;
@@ -607,24 +619,24 @@ private:
 };
 
 
 // used when iterating over all fonts looking for a match for a given character
 struct GlobalFontMatch {
     GlobalFontMatch(const uint32_t aCharacter,
                     const gfxFontStyle *aStyle) :
         mCh(aCharacter), mStyle(aStyle),
-        mMatchRank(0), mCount(0), mCmapsTested(0)
+        mMatchRank(0.0f), mCount(0), mCmapsTested(0)
         {
 
         }
 
     const uint32_t         mCh;          // codepoint to be matched
     const gfxFontStyle*    mStyle;       // style to match
-    int32_t                mMatchRank;   // metric indicating closest match
+    float                  mMatchRank;   // metric indicating closest match
     RefPtr<gfxFontEntry> mBestMatch;   // current best match
     RefPtr<gfxFontFamily> mMatchedFamily; // the family it belongs to
     uint32_t               mCount;       // number of fonts matched
     uint32_t               mCmapsTested; // number of cmaps tested
 };
 
 class gfxFontFamily {
 public:
--- a/gfx/thebes/gfxFontUtils.cpp
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -1939,34 +1939,16 @@ gfxFontUtils::GetVariationInstances(gfxF
             value.mAxis = axes[j].axisTag;
             value.mValue = int32_t(coords[j]) / 65536.0;
             instance.mValues.AppendElement(value);
         }
         aInstances.AppendElement(instance);
     }
 }
 
-void
-gfxFontUtils::MergeVariations(const nsTArray<gfxFontVariation>& aEntrySettings,
-                              const nsTArray<gfxFontVariation>& aStyleSettings,
-                              nsTArray<gfxFontVariation>* aMerged)
-{
-    MOZ_ASSERT(!aEntrySettings.IsEmpty() &&
-               !aStyleSettings.IsEmpty() &&
-               aMerged->IsEmpty());
-    // Settings from the CSS style will take precedence over those from the
-    // font entry (i.e. from the @font-face descriptor).
-    aMerged->AppendElements(aStyleSettings);
-    for (auto& setting : aEntrySettings) {
-        if (!aMerged->Contains(setting.mTag, VariationTagComparator())) {
-            aMerged->AppendElement(setting);
-        }
-    }
-}
-
 #ifdef XP_WIN
 
 /* static */
 bool
 gfxFontUtils::IsCffFont(const uint8_t* aFontData)
 {
     // this is only called after aFontData has passed basic validation,
     // so we know there is enough data present to allow us to read the version!
--- a/gfx/thebes/gfxFontUtils.h
+++ b/gfx/thebes/gfxFontUtils.h
@@ -1005,32 +1005,16 @@ public:
     // platforms where the native font APIs don't provide the info we want
     // in a convenient form.
     // (Not used on platforms -- currently, freetype -- where the font APIs
     // expose variation instance details directly.)
     static void
     GetVariationInstances(gfxFontEntry* aFontEntry,
                           nsTArray<gfxFontVariationInstance>& aInstances);
 
-    // Merge a list of font-variation-settings from a font entry and a list
-    // from a gfxFontStyle, to get a combined collection of settings that can
-    // be used to instantiate a font.
-    static void
-    MergeVariations(const nsTArray<gfxFontVariation>& aEntrySettings,
-                    const nsTArray<gfxFontVariation>& aStyleSettings,
-                    nsTArray<gfxFontVariation>* aMerged);
-
-    // Helper used by MergeVariations, and other code that wants to check
-    // whether an array of variation settings includes a particular tag.
-    struct VariationTagComparator {
-        bool Equals(const gfxFontVariation& aVariation, uint32_t aTag) const {
-            return aVariation.mTag == aTag;
-        }
-    };
-
 protected:
     friend struct MacCharsetMappingComparator;
 
     static nsresult
     ReadNames(const char *aNameData, uint32_t aDataLen, uint32_t aNameID,
               int32_t aLangID, int32_t aPlatformID, nsTArray<nsString>& aNames);
 
     // convert opentype name-table platform/encoding/language values to an
--- a/gfx/thebes/gfxGDIFont.cpp
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -455,17 +455,20 @@ gfxGDIFont::FillLogFont(LOGFONTW& aLogFo
             // choice of actual face used (bug 724231)
             weight = 0;
         } else {
             // avoid GDI synthetic bold which occurs when weight
             // specified is >= font data weight + 200
             weight = mNeedsBold ? 700 : 200;
         }
     } else {
-        weight = mNeedsBold ? 700 : fe->Weight().ToIntRounded();
+        // GDI doesn't support variation fonts, so for system fonts we know
+        // that the entry has only a single weight, not a range.
+        MOZ_ASSERT(fe->Weight().IsSingle());
+        weight = mNeedsBold ? 700 : fe->Weight().Min().ToIntRounded();
     }
 
     fe->FillLogFont(&aLogFont, weight, aSize);
 }
 
 uint32_t
 gfxGDIFont::GetGlyph(uint32_t aUnicode, uint32_t aVarSelector)
 {
--- a/gfx/thebes/gfxGDIFontList.cpp
+++ b/gfx/thebes/gfxGDIFontList.cpp
@@ -121,30 +121,32 @@ GDIFontEntry::GDIFontEntry(const nsAStri
                            gfxUserFontData *aUserFontData)
     : gfxFontEntry(aFaceName),
       mFontType(aFontType),
       mForceGDI(false),
       mUnicodeRanges()
 {
     mUserFontData.reset(aUserFontData);
     mStyle = aStyle;
-    mWeight = aWeight;
+    mWeightRange = WeightRange(aWeight);
     mStretch = aStretch;
     if (IsType1())
         mForceGDI = true;
     mIsDataUserFont = aUserFontData != nullptr;
 
     InitLogFont(aFaceName, aFontType);
 }
 
 gfxFontEntry*
 GDIFontEntry::Clone() const
 {
     MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
-    return new GDIFontEntry(Name(), mFontType, mStyle, mWeight, mStretch,
+    // GDI fonts don't support variations, so we don't need to worry about
+    // cloning the weight as a range.
+    return new GDIFontEntry(Name(), mFontType, mStyle, Weight().Min(), mStretch,
                             nullptr);
 }
 
 nsresult
 GDIFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
 {
     AUTO_PROFILER_LABEL("GDIFontEntry::ReadCMAP", OTHER);
 
@@ -300,17 +302,17 @@ GDIFontEntry::TestCharacterMap(uint32_t 
         if (aCh > 0xFFFF)
             return false;
 
         // previous code was using the group style
         gfxFontStyle fakeStyle;
         if (!IsUpright()) {
             fakeStyle.style = FontSlantStyle::Italic();
         }
-        fakeStyle.weight = mWeight;
+        fakeStyle.weight = Weight().Min();
 
         RefPtr<gfxFont> tempFont = FindOrMakeFont(&fakeStyle, false);
         if (!tempFont || !tempFont->Valid())
             return false;
         gfxGDIFont *font = static_cast<gfxGDIFont*>(tempFont.get());
 
         HDC dc = GetDC((HWND)nullptr);
         SetGraphicsMode(dc, GM_ADVANCED);
@@ -379,17 +381,17 @@ GDIFontEntry::InitLogFont(const nsAStrin
     mLogFont.lfClipPrecision  = CLIP_TURNOFF_FONTASSOCIATION;
     mLogFont.lfQuality        = DEFAULT_QUALITY;
     mLogFont.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
     // always force lfItalic if we want it.  Font selection code will
     // do its best to give us an italic font entry, but if no face exists
     // it may give us a regular one based on weight.  Windows should
     // do fake italic for us in that case.
     mLogFont.lfItalic         = !IsUpright();
-    mLogFont.lfWeight         = int(mWeight.ToFloat());
+    mLogFont.lfWeight         = Weight().Min().ToIntRounded();
 
     int len = std::min<int>(aName.Length(), LF_FACESIZE - 1);
     memcpy(&mLogFont.lfFaceName, aName.BeginReading(), len * sizeof(char16_t));
     mLogFont.lfFaceName[len] = '\0';
 }
 
 GDIFontEntry* 
 GDIFontEntry::CreateFontEntry(const nsAString& aName,
@@ -462,17 +464,17 @@ GDIFontFamily::FamilyAddStylesProc(const
             // otherwise if the new type is worse, skip it
             return 1;
         }
     }
 
     for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) {
         fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get());
         // check if we already know about this face
-        if (fe->mWeight == FontWeight(int32_t(logFont.lfWeight)) &&
+        if (fe->Weight().Min() == FontWeight(int32_t(logFont.lfWeight)) &&
             fe->IsItalic() == (logFont.lfItalic == 0xFF)) {
             // update the charset bit here since this could be different
             // XXX Can we still do this now that we store mCharset
             // on the font family rather than the font entry?
             ff->mCharset.set(metrics.tmCharSet);
             return 1; 
         }
     }
@@ -728,25 +730,25 @@ gfxGDIFontList::LookupLocalFont(const ns
     bool isCFF = false; // jtdfix -- need to determine this
     
     // use the face name from the lookup font entry, which will be the localized
     // face name which GDI mapping tables use (e.g. with the system locale set to
     // Dutch, a fullname of 'Arial Bold' will find a font entry with the face name
     // 'Arial Vet' which can be used as a key in GDI font lookups).
     GDIFontEntry *fe = GDIFontEntry::CreateFontEntry(lookup->Name(), 
         gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE : GFX_FONT_TYPE_TRUETYPE) /*type*/, 
-        lookup->mStyle, lookup->mWeight, aStretch, nullptr);
+        lookup->mStyle, lookup->Weight().Min(), aStretch, nullptr);
 
     if (!fe)
         return nullptr;
 
     fe->mIsLocalUserFont = true;
 
     // make the new font entry match the userfont entry style characteristics
-    fe->mWeight = aWeight;
+    fe->mWeightRange = WeightRange(aWeight);
     fe->mStyle = aStyle;
 
     return fe;
 }
 
 // If aFontData contains only a MS/Symbol cmap subtable, not MS/Unicode,
 // we modify the subtable header to mark it as Unicode instead, because
 // otherwise GDI will refuse to load the font.
--- a/gfx/thebes/gfxMacFont.cpp
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -32,43 +32,33 @@ gfxMacFont::gfxMacFont(const RefPtr<Unsc
       mCTFont(nullptr),
       mFontFace(nullptr),
       mFontSmoothingBackgroundColor(aFontStyle->fontSmoothingBackgroundColor),
       mVariationFont(aFontEntry->HasVariations())
 {
     mApplySyntheticBold = aNeedsBold;
 
     if (mVariationFont && (!aFontStyle->variationSettings.IsEmpty() ||
-                           !aFontEntry->mVariationSettings.IsEmpty())) {
+                           !aFontEntry->mVariationSettings.IsEmpty() ||
+                           !aFontEntry->Weight().IsSingle())) {
         CGFontRef baseFont = aUnscaledFont->GetFont();
         if (!baseFont) {
             mIsValid = false;
             return;
         }
 
-        // Probably one of the lists of variations, either from the @font-face
-        // descriptor or from the property, will be empty. So skip merging them
-        // unless really necessary.
-        const nsTArray<gfxFontVariation>* vars;
-        AutoTArray<gfxFontVariation,4> mergedSettings;
-        if (aFontStyle->variationSettings.IsEmpty()) {
-            vars = &aFontEntry->mVariationSettings;
-        } else if (aFontEntry->mVariationSettings.IsEmpty()) {
-            vars = &aFontStyle->variationSettings;
-        } else {
-            gfxFontUtils::MergeVariations(aFontEntry->mVariationSettings,
-                                          aFontStyle->variationSettings,
-                                          &mergedSettings);
-            vars = &mergedSettings;
-        }
+        // Get the variation settings needed to instantiate the fontEntry
+        // for a particular fontStyle.
+        AutoTArray<gfxFontVariation,4> vars;
+        aFontEntry->GetVariationsForStyle(vars, *aFontStyle);
 
         mCGFont =
             UnscaledFontMac::CreateCGFontWithVariations(baseFont,
-                                                        vars->Length(),
-                                                        vars->Elements());
+                                                        vars.Length(),
+                                                        vars.Elements());
         if (!mCGFont) {
           ::CFRetain(baseFont);
           mCGFont = baseFont;
         }
     } else {
         mCGFont = aUnscaledFont->GetFont();
         if (!mCGFont) {
             mIsValid = false;
--- a/gfx/thebes/gfxMacPlatformFontList.mm
+++ b/gfx/thebes/gfxMacPlatformFontList.mm
@@ -378,17 +378,17 @@ MacOSFontEntry::MacOSFontEntry(const nsA
       mHasVariationsInitialized(false),
       mHasAATSmallCaps(false),
       mHasAATSmallCapsInitialized(false),
       mCheckedForTracking(false),
       mTrakTable(nullptr),
       mTrakValues(nullptr),
       mTrakSizeTable(nullptr)
 {
-    mWeight = aWeight;
+    mWeightRange = WeightRange(aWeight);
 }
 
 MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
                                CGFontRef aFontRef,
                                FontWeight aWeight,
                                FontStretch aStretch,
                                FontSlantStyle aStyle,
                                bool aIsDataUserFont,
@@ -408,35 +408,36 @@ MacOSFontEntry::MacOSFontEntry(const nsA
       mTrakTable(nullptr),
       mTrakValues(nullptr),
       mTrakSizeTable(nullptr)
 {
     mFontRef = aFontRef;
     mFontRefInitialized = true;
     ::CFRetain(mFontRef);
 
-    mWeight = aWeight;
+    mWeightRange = WeightRange(aWeight);
     mStretch = aStretch;
     mFixedPitch = false; // xxx - do we need this for downloaded fonts?
     mStyle = aStyle;
 
     NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont),
                  "userfont is either a data font or a local font");
     mIsDataUserFont = aIsDataUserFont;
     mIsLocalUserFont = aIsLocalUserFont;
 }
 
 gfxFontEntry*
 MacOSFontEntry::Clone() const
 {
     MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
     MacOSFontEntry* fe =
-        new MacOSFontEntry(Name(), mWeight, mStandardFace, mSizeHint);
+        new MacOSFontEntry(Name(), Weight().Min(), mStandardFace, mSizeHint);
     fe->mStyle = mStyle;
     fe->mStretch = mStretch;
+    fe->mWeightRange = mWeightRange;
     fe->mFixedPitch = mFixedPitch;
     return fe;
 }
 
 CGFontRef
 MacOSFontEntry::GetFontRef()
 {
     if (!mFontRefInitialized) {
@@ -923,24 +924,29 @@ gfxMacFontFamily::FindStyleVariations(Fo
             [facename hasSuffix:@"Oblique"])
         {
             fontEntry->mStyle = FontSlantStyle::Italic();
         }
         if (macTraits & NSFixedPitchFontMask) {
             fontEntry->mFixedPitch = true;
         }
 
+        fontEntry->SetupVariationRanges();
+
         if (LOG_FONTLIST_ENABLED()) {
+            nsAutoCString weightString;
+            fontEntry->Weight().ToString(weightString);
             LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
-                 " with style: %s weight: %d stretch: %g%%"
+                 " with style: %s weight: %s stretch: %g%%"
                  " (apple-weight: %d macTraits: %8.8x)",
                  NS_ConvertUTF16toUTF8(fontEntry->Name()).get(),
                  NS_ConvertUTF16toUTF8(Name()).get(),
                  fontEntry->IsItalic() ? "italic" : "normal",
-                 cssWeight, fontEntry->Stretch().Percentage(),
+                 weightString.get(),
+                 fontEntry->Stretch().Percentage(),
                  appKitWeight, macTraits));
         }
 
         // insert into font entry array of family
         AddFontEntry(fontEntry);
     }
 
     SortAvailableFonts();
@@ -1277,19 +1283,20 @@ gfxMacPlatformFontList::InitSingleFaceLi
 
         // add only if doesn't exist already
         if (!mFontFamilies.GetWeak(key)) {
             RefPtr<gfxFontFamily> familyEntry =
                 new gfxSingleFaceMacFontFamily(familyName);
             // We need a separate font entry, because its family name will
             // differ from the one we found in the main list.
             MacOSFontEntry* fontEntry =
-                new MacOSFontEntry(fe->Name(), fe->mWeight, true,
+                new MacOSFontEntry(fe->Name(), fe->Weight().Min(), true,
                                    static_cast<const MacOSFontEntry*>(fe)->
                                        mSizeHint);
+            fontEntry->mWeightRange = fe->mWeightRange;
             familyEntry->AddFontEntry(fontEntry);
             familyEntry->SetHasStyles(true);
             mFontFamilies.Put(key, familyEntry);
             LOG_FONTLIST(("(fontlist-singleface) added new family: %s, key: %s\n",
                           NS_ConvertUTF16toUTF8(familyName).get(),
                           NS_ConvertUTF16toUTF8(key).get()));
         }
     }
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -121,17 +121,17 @@ gfxUserFontEntry::gfxUserFontEntry(gfxUs
       mLoader(nullptr),
       mFontSet(aFontSet)
 {
     MOZ_ASSERT(aWeight.ToFloat() != 0.0f,
                "aWeight must not be 0; use FontWeight::Normal() instead");
     mIsUserFontContainer = true;
     mSrcList = aFontFaceSrcList;
     mSrcIndex = 0;
-    mWeight = aWeight;
+    mWeightRange = WeightRange(aWeight);
     mStretch = aStretch;
     mStyle = aStyle;
     mFeatureSettings.AppendElements(aFeatureSettings);
     mVariationSettings.AppendElements(aVariationSettings);
     mLanguageOverride = aLanguageOverride;
     mCharacterMap = aUnicodeRanges;
 }
 
@@ -149,17 +149,18 @@ gfxUserFontEntry::Matches(const nsTArray
                           FontStretch aStretch,
                           FontSlantStyle aStyle,
                           const nsTArray<gfxFontFeature>& aFeatureSettings,
                           const nsTArray<gfxFontVariation>& aVariationSettings,
                           uint32_t aLanguageOverride,
                           gfxCharacterMap* aUnicodeRanges,
                           uint8_t aFontDisplay)
 {
-    return mWeight == aWeight &&
+    return Weight().Min() == aWeight &&
+           Weight().Max() == aWeight &&
            mStretch == aStretch &&
            mStyle == aStyle &&
            mFeatureSettings == aFeatureSettings &&
            mVariationSettings == aVariationSettings &&
            mLanguageOverride == aLanguageOverride &&
            mSrcList == aFontFaceSrcList &&
            mFontDisplay == aFontDisplay &&
            ((!aUnicodeRanges && !mCharacterMap) ||
@@ -511,17 +512,17 @@ gfxUserFontEntry::DoLoadNextSrc(bool aFo
         // src local ==> lookup and load immediately
 
         if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) {
             // Don't look up local fonts if the font whitelist is being used.
             gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
             gfxFontEntry* fe = pfl && pfl->IsFontFamilyWhitelistActive() ?
                 nullptr :
                 gfxPlatform::GetPlatform()->LookupLocalFont(currSrc.mLocalName,
-                                                            mWeight,
+                                                            Weight().Min(),
                                                             mStretch,
                                                             mStyle);
             nsTArray<gfxUserFontSet*> fontSets;
             GetUserFontSets(fontSets);
             for (gfxUserFontSet* fontSet : fontSets) {
                 // We need to note on each gfxUserFontSet that contains the user
                 // font entry that we used a local() rule.
                 fontSet->SetLocalRulesUsed();
@@ -766,17 +767,17 @@ gfxUserFontEntry::LoadPlatformFont(const
         // don't allow us to retrieve or measure it directly.
         // The *OnAlloc function will also tell DMD about this block, as the
         // OS font code may hold on to it for an extended period.
         computedSize = UserFontMallocSizeOfOnAlloc(saneData);
 
         // Here ownership of saneData is passed to the platform,
         // which will delete it when no longer required
         fe = gfxPlatform::GetPlatform()->MakePlatformFont(mName,
-                                                          mWeight,
+                                                          Weight().Min(),
                                                           mStretch,
                                                           mStyle,
                                                           saneData,
                                                           saneLen);
         if (!fe) {
             mFontSet->LogMessage(this, "not usable by platform");
         }
     }
@@ -1035,22 +1036,24 @@ gfxUserFontSet::FindExistingUserFontEntr
 void
 gfxUserFontSet::AddUserFontEntry(const nsAString& aFamilyName,
                                  gfxUserFontEntry* aUserFontEntry)
 {
     gfxUserFontFamily* family = GetFamily(aFamilyName);
     family->AddFontEntry(aUserFontEntry);
 
     if (LOG_ENABLED()) {
-        LOG(("userfonts (%p) added to \"%s\" (%p) style: %s weight: %g "
+        nsAutoCString weightString;
+        aUserFontEntry->Weight().ToString(weightString);
+        LOG(("userfonts (%p) added to \"%s\" (%p) style: %s weight: %s "
              "stretch: %g%% display: %d",
              this, NS_ConvertUTF16toUTF8(aFamilyName).get(), aUserFontEntry,
              (aUserFontEntry->IsItalic() ? "italic" :
               (aUserFontEntry->IsOblique() ? "oblique" : "normal")),
-             aUserFontEntry->Weight().ToFloat(),
+             weightString.get(),
              aUserFontEntry->Stretch().Percentage(),
              aUserFontEntry->GetFontDisplay()));
     }
 }
 
 void
 gfxUserFontSet::IncrementGeneration(bool aIsRebuild)
 {
@@ -1178,17 +1181,17 @@ gfxUserFontSet::UserFontCache::Entry::Ke
         }
     }
 
     if (mPrivate != aKey->mPrivate) {
         return false;
     }
 
     if (mFontEntry->mStyle            != fe->mStyle     ||
-        mFontEntry->mWeight           != fe->mWeight          ||
+        mFontEntry->Weight()          != fe->Weight()         ||
         mFontEntry->mStretch          != fe->mStretch         ||
         mFontEntry->mFeatureSettings  != fe->mFeatureSettings ||
         mFontEntry->mVariationSettings != fe->mVariationSettings ||
         mFontEntry->mLanguageOverride != fe->mLanguageOverride ||
         mFontEntry->mFamilyName       != fe->mFamilyName) {
         return false;
     }
 
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -403,17 +403,18 @@ public:
             static PLDHashNumber HashKey(const KeyTypePointer aKey) {
                 PLDHashNumber principalHash =
                     aKey->mPrincipal ? aKey->mPrincipal->Hash() : 0;
                 return mozilla::HashGeneric(principalHash + int(aKey->mPrivate),
                                             aKey->mURI->Hash(),
                                             HashFeatures(aKey->mFontEntry->mFeatureSettings),
                                             HashVariations(aKey->mFontEntry->mVariationSettings),
                                             mozilla::HashString(aKey->mFontEntry->mFamilyName),
-                                            aKey->mFontEntry->mWeight.ForHash(),
+                                            aKey->mFontEntry->Weight().Min().ForHash(),
+                                            aKey->mFontEntry->Weight().Max().ForHash(),
                                             // XXX Is this right?
                                             aKey->mFontEntry->mStyle.ForHash(),
                                             aKey->mFontEntry->mStretch.ForHash(),
                                             aKey->mFontEntry->mLanguageOverride);
             }
 
             enum { ALLOW_MEMMOVE = false };
 
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -1282,23 +1282,25 @@ FontFaceSet::LogMessage(gfxUserFontEntry
   if (!console) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsAutoCString familyName;
   nsAutoCString fontURI;
   aUserFontEntry->GetFamilyNameAndURIForLogging(familyName, fontURI);
 
+  nsAutoCString weightString;
+  aUserFontEntry->Weight().ToString(weightString);
   nsPrintfCString message
        ("downloadable font: %s "
-        "(font-family: \"%s\" style:%s weight:%g stretch:%g%% src index:%d)",
+        "(font-family: \"%s\" style:%s weight:%s stretch:%g%% src index:%d)",
         aMessage,
         familyName.get(),
         aUserFontEntry->IsItalic() ? "italic" : "normal",
-        aUserFontEntry->Weight().ToFloat(),
+        weightString.get(),
         aUserFontEntry->Stretch().Percentage(),
         aUserFontEntry->GetSrcIndex());
 
   if (NS_FAILED(aStatus)) {
     message.AppendLiteral(": ");
     switch (aStatus) {
     case NS_ERROR_DOM_BAD_URI:
       message.AppendLiteral("bad URI or cross-site access not allowed");