diff --git a/gfx/thebes/SharedFontList.h b/gfx/thebes/SharedFontList.h --- a/gfx/thebes/SharedFontList.h +++ b/gfx/thebes/SharedFontList.h @@ -157,7 +157,10 @@ nsCString mDescriptor; // descriptor that can be used to instantiate a // platform font reference uint16_t mIndex; // an index used with descriptor (on some platforms) - bool mFixedPitch; // is the face fixed-pitch (monospaced)? +#ifdef MOZ_WIDGET_GTK + uint16_t mSize; // pixel size if bitmap; zero indicates scalable +#endif + bool mFixedPitch; // is the face fixed-pitch (monospaced)? mozilla::WeightRange mWeight; // CSS font-weight value mozilla::StretchRange mStretch; // CSS font-stretch value mozilla::SlantStyleRange mStyle; // CSS font-style value @@ -169,11 +172,15 @@ Face(FontList* aList, const InitData& aData) : mDescriptor(aList, aData.mDescriptor), mIndex(aData.mIndex), +#ifdef MOZ_WIDGET_GTK + mSize(aData.mSize), +#endif mFixedPitch(aData.mFixedPitch), mWeight(aData.mWeight), mStretch(aData.mStretch), mStyle(aData.mStyle), - mCharacterMap(Pointer::Null()) {} + mCharacterMap(Pointer::Null()) { + } bool HasValidDescriptor() const { return !mDescriptor.IsNull() && mIndex != uint16_t(-1); @@ -183,6 +190,9 @@ String mDescriptor; uint16_t mIndex; +#ifdef MOZ_WIDGET_GTK + uint16_t mSize; +#endif bool mFixedPitch; mozilla::WeightRange mWeight; mozilla::StretchRange mStretch; @@ -309,6 +319,11 @@ void SetupFamilyCharMap(FontList* aList); private: + // Returns true if there are specifically-sized bitmap faces in the list, + // so size selection still needs to be done. (Currently only on Linux.) + bool FindAllFacesForStyleInternal(FontList* aList, const gfxFontStyle& aStyle, + nsTArray& aFaceList) const; + std::atomic mFaceCount; String mKey; String mName; diff --git a/gfx/thebes/SharedFontList.cpp b/gfx/thebes/SharedFontList.cpp --- a/gfx/thebes/SharedFontList.cpp +++ b/gfx/thebes/SharedFontList.cpp @@ -11,6 +11,7 @@ #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/Logging.h" +#include "mozilla/Unused.h" #define LOG_FONTLIST(args) \ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args) @@ -231,34 +232,46 @@ } } -void Family::FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle, - nsTArray& aFaceList, - bool aIgnoreSizeTolerance) const { +bool Family::FindAllFacesForStyleInternal(FontList* aList, + const gfxFontStyle& aStyle, + nsTArray& aFaceList) const { MOZ_ASSERT(aFaceList.IsEmpty()); if (!IsInitialized()) { - return; + return false; } Pointer* facePtrs = Faces(aList); if (!facePtrs) { - return; + return false; } - // If the family has only one face, we simply return it; no further - // checking needed. + // Depending on the kind of family, we have to do varying amounts of work + // to figure out what face(s) to use for the requested style properties. + + // If the family has only one face, we simply use it; no further style + // checking needed. (However, for bitmap fonts we may still need to check + // whether the size is acceptable.) if (NumFaces() == 1) { MOZ_ASSERT(!facePtrs[0].IsNull()); - aFaceList.AppendElement(static_cast(facePtrs[0].ToPtr(aList))); - return; + Face* face = static_cast(facePtrs[0].ToPtr(aList)); + if (face && face->HasValidDescriptor()) { + aFaceList.AppendElement(face); +#ifdef MOZ_WIDGET_GTK + if (face->mSize) { + return true; + } +#endif + } + return false; } // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, // or some subset of these. In this case, we have exactly 4 entries in // mAvailableFonts, stored in the above order; note that some of the entries // may be nullptr. We can then pick the required entry based on whether the - // request is for bold or non-bold, italic or non-italic, without running the - // more complex matching algorithm used for larger families with many weights - // and/or widths. + // request is for bold or non-bold, italic or non-italic, without running + // the more complex matching algorithm used for larger families with many + // weights and/or widths. if (mIsSimple) { // Family has no more than the "standard" 4 faces, at fixed indexes; @@ -270,15 +283,20 @@ uint8_t faceIndex = (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0); - // if the desired style is available, return it directly + // If the desired style is available, use it directly. Face* face = static_cast(facePtrs[faceIndex].ToPtr(aList)); if (face && face->HasValidDescriptor()) { aFaceList.AppendElement(face); - return; +#ifdef MOZ_WIDGET_GTK + if (face->mSize) { + return true; + } +#endif + return false; } - // order to check fallback faces in a simple family, depending on requested - // style + // Order to check fallback faces in a simple family, depending on the + // requested style. static const uint8_t simpleFallbacks[4][3] = { {kBoldFaceIndex, kItalicFaceIndex, kBoldItalicFaceIndex}, // fallback sequence for Regular @@ -294,7 +312,12 @@ face = static_cast(facePtrs[order[trial]].ToPtr(aList)); if (face && face->HasValidDescriptor()) { aFaceList.AppendElement(face); - return; +#ifdef MOZ_WIDGET_GTK + if (face->mSize) { + return true; + } +#endif + return false; } } @@ -302,7 +325,7 @@ // can happen if we're on a stylo thread and caught the font list being // updated; in that case we just fail quietly and let font fallback do // something for the time being. - return; + return false; } // Pick the font(s) that are closest to the desired weight, style, and @@ -315,9 +338,11 @@ // normal platform fonts with a single font entry for each // weight/style/stretch combination, only the last matched font entry will // be added. - double minDistance = INFINITY; Face* matched = nullptr; + // Keep track of whether we've included any non-scalable font resources in + // the selected set. + bool anyNonScalable = false; for (uint32_t i = 0; i < NumFaces(); i++) { Face* face = static_cast(facePtrs[i].ToPtr(aList)); if (face) { @@ -332,6 +357,11 @@ } else if (distance == minDistance) { if (matched) { aFaceList.AppendElement(matched); +#ifdef MOZ_WIDGET_GTK + if (matched->mSize) { + anyNonScalable = true; + } +#endif } matched = face; } @@ -341,7 +371,69 @@ MOZ_ASSERT(matched, "didn't match a font within a family"); if (matched) { aFaceList.AppendElement(matched); +#ifdef MOZ_WIDGET_GTK + if (matched->mSize) { + anyNonScalable = true; + } +#endif } + + return anyNonScalable; +} + +void Family::FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle, + nsTArray& aFaceList, + bool aIgnoreSizeTolerance) const { +#ifdef MOZ_WIDGET_GTK + bool anyNonScalable = +#else + Unused << +#endif + FindAllFacesForStyleInternal(aList, aStyle, aFaceList); + +#ifdef MOZ_WIDGET_GTK + // aFaceList now contains whatever faces are the best style match for + // the requested style. If specifically-sized bitmap faces are supported, + // we need to additionally filter the list to choose the appropriate size. + // + // It would be slightly more efficient to integrate this directly into the + // face-selection algorithm above, but it's a rare case that doesn't apply + // at all to most font families. + // + // Currently we only support pixel-sized bitmap font faces on Linux/Gtk (i.e. + // when using the gfxFcPlatformFontList implementation), so this filtering is + // not needed on other platforms. + // + // (Note that color-bitmap emoji fonts like Apple Color Emoji or Noto Color + // Emoji don't count here; they package multiple bitmap sizes into a single + // OpenType wrapper, so they appear as a single "scalable" face in our list.) + if (anyNonScalable) { + uint16_t best = 0; + gfxFloat dist = 0.0; + for (const auto& f : aFaceList) { + if (f->mSize == 0) { + // Scalable face; no size distance to compute. + continue; + } + gfxFloat d = fabs(gfxFloat(f->mSize) - aStyle.size); + if (!aIgnoreSizeTolerance && (d * 5.0 > f->mSize)) { + continue; // Too far from the requested size, ignore. + } + // If we haven't found a "best" bitmap size yet, or if this is a better + // match, remember it. + if (!best || d < dist) { + best = f->mSize; + dist = d; + } + } + // Discard all faces except the chosen "best" size; or if no pixel size was + // chosen, all except scalable faces. + // This may eliminate *all* faces in the family, if all were bitmaps and + // none was a good enough size match, in which case we'll fall back to the + // next font-family name. + aFaceList.RemoveElementsBy([=](const auto& e) { return e->mSize != best; }); + } +#endif } Face* Family::FindFaceForStyle(FontList* aList, const gfxFontStyle& aStyle, diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp --- a/gfx/thebes/gfxFcPlatformFontList.cpp +++ b/gfx/thebes/gfxFcPlatformFontList.cpp @@ -205,7 +205,8 @@ static void GetFontProperties(FcPattern* aFontPattern, WeightRange* aWeight, StretchRange* aStretch, - SlantStyleRange* aSlantStyle) { + SlantStyleRange* aSlantStyle, + uint16_t* aSize = nullptr) { // weight int weight; if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) != @@ -231,6 +232,24 @@ } else if (slant > 0) { *aSlantStyle = SlantStyleRange(FontSlantStyle::Italic()); } + + if (aSize) { + // pixel size, or zero if scalable + FcBool scalable; + if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) == + FcResultMatch && + scalable) { + *aSize = 0; + } else { + double size; + if (FcPatternGetDouble(aFontPattern, FC_PIXEL_SIZE, 0, &size) == + FcResultMatch) { + *aSize = uint16_t(NS_round(size)); + } else { + *aSize = 0; + } + } + } } gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName, @@ -1667,10 +1686,11 @@ WeightRange weight(FontWeight::Normal()); StretchRange stretch(FontStretch::Normal()); SlantStyleRange style(FontSlantStyle::Normal()); - GetFontProperties(aPattern, &weight, &stretch, &style); + uint16_t size; + GetFontProperties(aPattern, &weight, &stretch, &style, &size); - auto initData = - fontlist::Face::InitData{descriptor, 0, false, weight, stretch, style}; + auto initData = fontlist::Face::InitData{descriptor, 0, size, false, + weight, stretch, style}; // Add entries for any other localized family names. (Most fonts only have // a single family name, so the first call to GetString will usually fail).