Bug 1350783 - Support bitmaps fonts in gfxFcPlatformFontList. r=jfkthame, a=gchang
authorLee Salzman <lsalzman@mozilla.com>
Wed, 29 Mar 2017 13:47:46 -0400
changeset 395640 7a069a170c51b1b42f85a2d82a416811c3d5e054
parent 395639 f82da404520a3a60a58b8f7f2df5db9edd96bebb
child 395641 5995da420038dd34a7f74ea9bc35fa2f713fd478
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame, gchang
bugs1350783
milestone54.0a2
Bug 1350783 - Support bitmaps fonts in gfxFcPlatformFontList. r=jfkthame, a=gchang MozReview-Commit-ID: 4VQkyhx4IJE
gfx/skia/skia/src/ports/SkFontHost_cairo.cpp
gfx/thebes/gfxFcPlatformFontList.cpp
gfx/thebes/gfxFcPlatformFontList.h
gfx/thebes/gfxFontEntry.h
--- a/gfx/skia/skia/src/ports/SkFontHost_cairo.cpp
+++ b/gfx/skia/skia/src/ports/SkFontHost_cairo.cpp
@@ -126,17 +126,18 @@ private:
     void resolvePattern(FcPattern* pattern);
 #endif
 
     cairo_scaled_font_t* fScaledFont;
     FT_Int32 fLoadGlyphFlags;
     FT_LcdFilter fLcdFilter;
     SkScalar fScaleX;
     SkScalar fScaleY;
-    FT_Matrix fShapeMatrix;
+    SkMatrix fShapeMatrix;
+    FT_Matrix fShapeMatrixFT;
     bool fHaveShape;
 };
 
 class CairoLockedFTFace {
 public:
     CairoLockedFTFace(cairo_scaled_font_t* scaledFont)
         : fScaledFont(scaledFont)
         , fFace(cairo_ft_scaled_font_lock_face(scaledFont))
@@ -587,22 +588,23 @@ bool SkScalerContext_CairoFT::computeSha
         fHaveShape = !m.isScaleTranslate();
     }
 
     fScaleX = SkDoubleToScalar(major);
     fScaleY = SkDoubleToScalar(minor);
 
     if (fHaveShape) {
         // Normalize the transform and convert to fixed-point.
-        double invScaleX = 65536.0 / major;
-        double invScaleY = 65536.0 / minor;
-        fShapeMatrix.xx = (FT_Fixed)(scaleX * invScaleX);
-        fShapeMatrix.yx = -(FT_Fixed)(skewY * invScaleX);
-        fShapeMatrix.xy = -(FT_Fixed)(skewX * invScaleY);
-        fShapeMatrix.yy = (FT_Fixed)(scaleY * invScaleY);
+        fShapeMatrix = m;
+        fShapeMatrix.preScale(SkDoubleToScalar(1.0 / major), SkDoubleToScalar(1.0 / minor));
+
+        fShapeMatrixFT.xx = SkScalarToFixed(fShapeMatrix.getScaleX());
+        fShapeMatrixFT.yx = SkScalarToFixed(-fShapeMatrix.getSkewY());
+        fShapeMatrixFT.xy = SkScalarToFixed(-fShapeMatrix.getSkewX());
+        fShapeMatrixFT.yy = SkScalarToFixed(fShapeMatrix.getScaleY());
     }
     return true;
 }
 
 unsigned SkScalerContext_CairoFT::generateGlyphCount()
 {
     CairoLockedFTFace faceLock(fScaledFont);
     return faceLock.getFace()->num_glyphs;
@@ -632,17 +634,17 @@ void SkScalerContext_CairoFT::prepareGly
 
 void SkScalerContext_CairoFT::fixVerticalLayoutBearing(FT_GlyphSlot glyph)
 {
     FT_Vector vector;
     vector.x = glyph->metrics.vertBearingX - glyph->metrics.horiBearingX;
     vector.y = -glyph->metrics.vertBearingY - glyph->metrics.horiBearingY;
     if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
         if (fHaveShape) {
-            FT_Vector_Transform(&vector, &fShapeMatrix);
+            FT_Vector_Transform(&vector, &fShapeMatrixFT);
         }
         FT_Outline_Translate(&glyph->outline, vector.x, vector.y);
     } else if (glyph->format == FT_GLYPH_FORMAT_BITMAP) {
         glyph->bitmap_left += SkFDot6Floor(vector.x);
         glyph->bitmap_top  += SkFDot6Floor(vector.y);
     }
 }
 
@@ -697,27 +699,30 @@ void SkScalerContext_CairoFT::generateMe
             glyph->fMaskFormat = SkMask::kARGB32_Format;
         }
 
         if (isLCD(fRec)) {
             fRec.fMaskFormat = SkMask::kA8_Format;
         }
 
         if (fHaveShape) {
+            // Ensure filtering is preserved when the bitmap is transformed.
+            // Otherwise, the result will look horrifically aliased.
+            if (fRec.fMaskFormat == SkMask::kBW_Format) {
+                fRec.fMaskFormat = SkMask::kA8_Format;
+            }
+
             // Apply the shape matrix to the glyph's bounding box.
-            SkMatrix matrix;
-            fRec.getSingleMatrix(&matrix);
-            matrix.preScale(SkScalarInvert(fScaleX), SkScalarInvert(fScaleY));
             SkRect srcRect = SkRect::MakeXYWH(
                 SkIntToScalar(face->glyph->bitmap_left),
                 -SkIntToScalar(face->glyph->bitmap_top),
                 SkIntToScalar(face->glyph->bitmap.width),
                 SkIntToScalar(face->glyph->bitmap.rows));
             SkRect destRect;
-            matrix.mapRect(&destRect, srcRect);
+            fShapeMatrix.mapRect(&destRect, srcRect);
             SkIRect glyphRect = destRect.roundOut();
             glyph->fWidth  = SkToU16(glyphRect.width());
             glyph->fHeight = SkToU16(glyphRect.height());
             glyph->fTop    = SkToS16(SkScalarRoundToInt(destRect.fTop));
             glyph->fLeft   = SkToS16(SkScalarRoundToInt(destRect.fLeft));
         } else {
             glyph->fWidth  = SkToU16(face->glyph->bitmap.width);
             glyph->fHeight = SkToU16(face->glyph->bitmap.rows);
@@ -760,18 +765,17 @@ void SkScalerContext_CairoFT::generateIm
         gSetLcdFilter;
     if (useLcdFilter) {
         gSetLcdFilter(face->glyph->library, fLcdFilter);
     }
 
     SkMatrix matrix;
     if (face->glyph->format == FT_GLYPH_FORMAT_BITMAP &&
         fHaveShape) {
-        matrix.setScale(SkIntToScalar(glyph.fWidth) / SkIntToScalar(face->glyph->bitmap.width),
-                        SkIntToScalar(glyph.fHeight) / SkIntToScalar(face->glyph->bitmap.rows));
+        matrix = fShapeMatrix;
     } else {
         matrix.setIdentity();
     }
     generateGlyphImage(face, glyph, matrix);
 
     if (useLcdFilter) {
         gSetLcdFilter(face->glyph->library, FT_LCD_FILTER_NONE);
     }
--- a/gfx/thebes/gfxFcPlatformFontList.cpp
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -791,44 +791,70 @@ PreparePattern(FcPattern* aPattern, bool
         }
 #endif // MOZ_X11
 #endif // MOZ_WIDGET_GTK
     }
 
     FcDefaultSubstitute(aPattern);
 }
 
+static inline gfxFloat
+SizeForStyle(gfxFontconfigFontEntry* aEntry, const gfxFontStyle& aStyle)
+{
+    return aStyle.sizeAdjust >= 0.0 ?
+                aStyle.GetAdjustedSize(aEntry->GetAspect()) :
+                aStyle.size;
+}
+
+static double
+ChooseFontSize(gfxFontconfigFontEntry* aEntry,
+               const gfxFontStyle& aStyle)
+{
+    double requestedSize = SizeForStyle(aEntry, aStyle);
+    double bestDist = -1.0;
+    double bestSize = requestedSize;
+    double size;
+    int v = 0;
+    while (FcPatternGetDouble(aEntry->GetPattern(),
+                              FC_PIXEL_SIZE, v, &size) == FcResultMatch) {
+        ++v;
+        double dist = fabs(size - requestedSize);
+        if (bestDist < 0.0 || dist < bestDist) {
+            bestDist = dist;
+            bestSize = size;
+        }
+    }
+    return bestSize;
+}
+
 gfxFont*
 gfxFontconfigFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle,
                                            bool aNeedsBold)
 {
     nsAutoRef<FcPattern> pattern(FcPatternCreate());
     if (!pattern) {
         NS_WARNING("Failed to create Fontconfig pattern for font instance");
         return nullptr;
     }
-    FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aFontStyle->size);
+
+    double size = ChooseFontSize(this, *aFontStyle);
+    FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size);
 
     PreparePattern(pattern, aFontStyle->printerFont);
     nsAutoRef<FcPattern> renderPattern
         (FcFontRenderPrepare(nullptr, pattern, mFontPattern));
     if (!renderPattern) {
         NS_WARNING("Failed to prepare Fontconfig pattern for font instance");
         return nullptr;
     }
 
-    double adjustedSize = aFontStyle->size;
-    if (aFontStyle->sizeAdjust >= 0.0) {
-        adjustedSize = aFontStyle->GetAdjustedSize(GetAspect());
-    }
-
     cairo_scaled_font_t* scaledFont =
-        CreateScaledFont(renderPattern, adjustedSize, aFontStyle, aNeedsBold);
+        CreateScaledFont(renderPattern, size, aFontStyle, aNeedsBold);
     gfxFont* newFont =
-        new gfxFontconfigFont(scaledFont, renderPattern, adjustedSize,
+        new gfxFontconfigFont(scaledFont, renderPattern, size,
                               this, aFontStyle, aNeedsBold);
     cairo_scaled_font_destroy(scaledFont);
 
     return newFont;
 }
 
 nsresult
 gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag,
@@ -926,20 +952,126 @@ gfxFontconfigFontFamily::FindStyleVariat
 }
 
 void
 gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern)
 {
     NS_ASSERTION(!mHasStyles,
                  "font patterns must not be added to already enumerated families");
 
+    FcBool scalable;
+    if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) != FcResultMatch ||
+        !scalable) {
+        mHasNonScalableFaces = true;
+    }
+
     nsCountedRef<FcPattern> pattern(aFontPattern);
     mFontPatterns.AppendElement(pattern);
 }
 
+static const double kRejectDistance = 10000.0;
+
+// Calculate a distance score representing the size disparity between the
+// requested style's size and the font entry's size.
+static double
+SizeDistance(gfxFontconfigFontEntry* aEntry, const gfxFontStyle& aStyle)
+{
+    double requestedSize = SizeForStyle(aEntry, aStyle);
+    double bestDist = -1.0;
+    double size;
+    int v = 0;
+    while (FcPatternGetDouble(aEntry->GetPattern(),
+                              FC_PIXEL_SIZE, v, &size) == FcResultMatch) {
+        ++v;
+        double dist = fabs(size - requestedSize);
+        if (bestDist < 0.0 || dist < bestDist) {
+            bestDist = dist;
+        }
+    }
+    if (bestDist < 0.0) {
+        // No size means scalable
+        return -1.0;
+    } else if (5.0 * bestDist < requestedSize) {
+        // fontconfig prefers a matching family or lang to pixelsize of bitmap
+        // fonts. CSS suggests a tolerance of 20% on pixelsize.
+        return bestDist;
+    } else {
+        // Reject any non-scalable fonts that are not within tolerance.
+        return kRejectDistance;
+    }
+}
+
+void
+gfxFontconfigFontFamily::FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
+                                              nsTArray<gfxFontEntry*>& aFontEntryList,
+                                              bool& aNeedsSyntheticBold)
+{
+    gfxFontFamily::FindAllFontsForStyle(aFontStyle,
+                                        aFontEntryList,
+                                        aNeedsSyntheticBold);
+
+    if (!mHasNonScalableFaces) {
+        return;
+    }
+
+    // Iterate over the the available fonts while compacting any groups
+    // of unscalable fonts with matching styles into a single entry
+    // corresponding to the closest available size. If the closest
+    // available size is rejected for being outside tolernace, then the
+    // entire group will be skipped.
+    size_t skipped = 0;
+    gfxFontconfigFontEntry* bestEntry = nullptr;
+    double bestDist = -1.0;
+    for (size_t i = 0; i < aFontEntryList.Length(); i++) {
+        gfxFontconfigFontEntry* entry =
+            static_cast<gfxFontconfigFontEntry*>(aFontEntryList[i]);
+        double dist = SizeDistance(entry, aFontStyle);
+        // 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->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) {
+                i -= skipped;
+                aFontEntryList.RemoveElementsAt(i, skipped);
+                skipped = 0;
+            }
+            // Mark the start of the new group.
+            bestEntry = entry;
+            bestDist = dist;
+        } else {
+            // If this entry more closely matches the requested size than the
+            // current best in the group, then take this entry instead.
+            if (dist < bestDist) {
+                aFontEntryList[i-1-skipped] = entry;
+                bestEntry = entry;
+                bestDist = dist;
+            }
+            skipped++;
+        }
+    }
+    // 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 current group.
+    if (skipped) {
+        aFontEntryList.TruncateLength(aFontEntryList.Length() - skipped);
+    }
+}
+
 gfxFontconfigFont::gfxFontconfigFont(cairo_scaled_font_t *aScaledFont,
                                      FcPattern *aPattern,
                                      gfxFloat aAdjustedSize,
                                      gfxFontEntry *aFontEntry,
                                      const gfxFontStyle *aFontStyle,
                                      bool aNeedsBold) :
     gfxFontconfigFontBase(aScaledFont, aPattern, aFontEntry, aFontStyle)
 {
@@ -1001,23 +1133,16 @@ gfxFcPlatformFontList::AddFontSetFamilie
     }
 
     FcChar8* lastFamilyName = (FcChar8*)"";
     RefPtr<gfxFontconfigFontFamily> fontFamily;
     nsAutoString familyName;
     for (int f = 0; f < aFontSet->nfont; f++) {
         FcPattern* font = aFontSet->fonts[f];
 
-        // not scalable? skip...
-        FcBool scalable;
-        if (FcPatternGetBool(font, FC_SCALABLE, 0, &scalable) != FcResultMatch ||
-            !scalable) {
-            continue;
-        }
-
         // get canonical name
         uint32_t cIndex = FindCanonicalNameIndex(font, FC_FAMILYLANG);
         FcChar8* canonical = nullptr;
         FcPatternGetString(font, FC_FAMILY, cIndex, &canonical);
         if (!canonical) {
             continue;
         }
 
@@ -1121,19 +1246,16 @@ GetSystemFontList(nsTArray<nsString>& aL
     // add the lang to the pattern
     nsAutoCString fcLang;
     gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
     pfl->GetSampleLangForGroup(aLangGroup, fcLang);
     if (!fcLang.IsEmpty()) {
         FcPatternAddString(pat, FC_LANG, ToFcChar8Ptr(fcLang.get()));
     }
 
-    // ignore size-specific fonts
-    FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
-
     nsAutoRef<FcFontSet> fs(FcFontList(nullptr, pat, os));
     if (!fs) {
         return;
     }
 
     for (int i = 0; i < fs->nfont; i++) {
         char *family;
 
@@ -1371,19 +1493,16 @@ gfxFcPlatformFontList::GetStandardFamily
         return true;
     }
 
     nsAutoRef<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr));
     if (!os) {
         return true;
     }
 
-    // ignore size-specific fonts
-    FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
-
     // add the family name to the pattern
     NS_ConvertUTF16toUTF8 familyName(aFontName);
     FcPatternAddString(pat, FC_FAMILY, ToFcChar8Ptr(familyName.get()));
 
     nsAutoRef<FcFontSet> givenFS(FcFontList(nullptr, pat, os));
     if (!givenFS) {
         return true;
     }
@@ -1610,23 +1729,16 @@ gfxFcPlatformFontList::FindGenericFamili
     // -- select the fonts to be used for the generic
     prefFonts = new PrefFontList; // can be empty but in practice won't happen
     uint32_t limit = gfxPlatformGtk::GetPlatform()->MaxGenericSubstitions();
     bool foundFontWithLang = false;
     for (int i = 0; i < faces->nfont; i++) {
         FcPattern* font = faces->fonts[i];
         FcChar8* mappedGeneric = nullptr;
 
-        // not scalable? skip...
-        FcBool scalable;
-        if (FcPatternGetBool(font, FC_SCALABLE, 0, &scalable) != FcResultMatch ||
-            !scalable) {
-            continue;
-        }
-
         FcPatternGetString(font, FC_FAMILY, 0, &mappedGeneric);
         if (mappedGeneric) {
             NS_ConvertUTF8toUTF16 mappedGenericName(ToCharPtr(mappedGeneric));
             AutoTArray<gfxFontFamily*,1> genericFamilies;
             if (gfxPlatformFontList::FindAndAddFamilies(mappedGenericName,
                                                         &genericFamilies)) {
                 MOZ_ASSERT(genericFamilies.Length() == 1,
                            "expected a single family");
--- a/gfx/thebes/gfxFcPlatformFontList.h
+++ b/gfx/thebes/gfxFcPlatformFontList.h
@@ -119,16 +119,18 @@ public:
     nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr) override;
     bool TestCharacterMap(uint32_t aCh) override;
 
     hb_blob_t* GetFontTable(uint32_t aTableTag) override;
 
     void ForgetHBFace() override;
     void ReleaseGrFace(gr_face* aFace) override;
 
+    double GetAspect();
+
 protected:
     virtual ~gfxFontconfigFontEntry();
 
     gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle,
                                 bool aNeedsBold) override;
 
     // helper method for creating cairo font from pattern
     cairo_scaled_font_t*
@@ -140,18 +142,16 @@ protected:
     // override to pull data from FTFace
     virtual nsresult
     CopyFontTable(uint32_t aTableTag,
                   nsTArray<uint8_t>& aBuffer) override;
 
     // if HB or GR faces are gone, close down the FT_Face
     void MaybeReleaseFTFace();
 
-    double GetAspect();
-
     // pattern for a single face of a family
     nsCountedRef<FcPattern> mFontPattern;
 
     // user font data, when needed
     RefPtr<FTUserFontData> mUserFontData;
 
     // FTFace - initialized when needed
     FT_Face   mFTFace;
@@ -169,36 +169,43 @@ protected:
     // data font
     const uint8_t* mFontData;
 };
 
 class gfxFontconfigFontFamily : public gfxFontFamily {
 public:
     explicit gfxFontconfigFontFamily(const nsAString& aName) :
         gfxFontFamily(aName),
-        mContainsAppFonts(false)
+        mContainsAppFonts(false),
+        mHasNonScalableFaces(false)
     { }
 
     void FindStyleVariations(FontInfoData *aFontInfoData = nullptr) override;
 
     // Families are constructed initially with just references to patterns.
     // When necessary, these are enumerated within FindStyleVariations.
     void AddFontPattern(FcPattern* aFontPattern);
 
     void SetFamilyContainsAppFonts(bool aContainsAppFonts)
     {
         mContainsAppFonts = aContainsAppFonts;
     }
 
+    void
+    FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
+                         nsTArray<gfxFontEntry*>& aFontEntryList,
+                         bool& aNeedsSyntheticBold) override;
+
 protected:
     virtual ~gfxFontconfigFontFamily() { }
 
     nsTArray<nsCountedRef<FcPattern> > mFontPatterns;
 
     bool      mContainsAppFonts;
+    bool      mHasNonScalableFaces;
 };
 
 class gfxFontconfigFont : public gfxFontconfigFontBase {
 public:
     gfxFontconfigFont(cairo_scaled_font_t *aScaledFont,
                       FcPattern *aPattern,
                       gfxFloat aAdjustedSize,
                       gfxFontEntry *aFontEntry,
--- a/gfx/thebes/gfxFontEntry.h
+++ b/gfx/thebes/gfxFontEntry.h
@@ -633,17 +633,17 @@ public:
     // choose a specific face to match a style using CSS font matching
     // rules (weight matching occurs here).  may return a face that doesn't
     // precisely match (e.g. normal face when no italic face exists).
     // aNeedsSyntheticBold is set to true when synthetic bolding is
     // needed, false otherwise
     gfxFontEntry *FindFontForStyle(const gfxFontStyle& aFontStyle, 
                                    bool& aNeedsSyntheticBold);
 
-    void
+    virtual void
     FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
                          nsTArray<gfxFontEntry*>& aFontEntryList,
                          bool& aNeedsSyntheticBold);
 
     // checks for a matching font within the family
     // used as part of the font fallback process
     void FindFontForChar(GlobalFontMatch *aMatchData);