bug 574907 - don't let DWrite use fractional font sizes and subpixel positioning when embedded bitmaps are present. r=masayuki a=joe
authorJonathan Kew <jfkthame@gmail.com>
Fri, 21 Jan 2011 10:36:28 +0000
changeset 61074 e0c9841ac3ddec2a86b8cbee70cba5039f71e5d1
parent 61073 02b92b61f5cdaa8efd8dfa5c8cb86d4a0f25aa01
child 61075 e0ea4e4d401ffb439fc87a6196c84302aa4b3ec8
push idunknown
push userunknown
push dateunknown
reviewersmasayuki, joe
bugs574907
milestone2.0b10pre
bug 574907 - don't let DWrite use fractional font sizes and subpixel positioning when embedded bitmaps are present. r=masayuki a=joe
gfx/thebes/gfxDWriteFonts.cpp
gfx/thebes/gfxDWriteFonts.h
gfx/thebes/gfxDWriteShaper.cpp
--- a/gfx/thebes/gfxDWriteFonts.cpp
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -119,17 +119,17 @@ gfxDWriteFont::gfxDWriteFont(gfxFontEntr
                              const gfxFontStyle *aFontStyle,
                              PRBool aNeedsBold,
                              AntialiasOption anAAOption)
     : gfxFont(aFontEntry, aFontStyle, anAAOption)
     , mCairoFontFace(nsnull)
     , mCairoScaledFont(nsnull)
     , mNeedsOblique(PR_FALSE)
     , mNeedsBold(aNeedsBold)
-    , mUsingClearType(PR_FALSE)
+    , mUseSubpixelPositions(PR_FALSE)
 {
     gfxDWriteFontEntry *fe =
         static_cast<gfxDWriteFontEntry*>(aFontEntry);
     nsresult rv;
     DWRITE_FONT_SIMULATIONS sims = DWRITE_FONT_SIMULATIONS_NONE;
     if ((GetStyle()->style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)) &&
         !fe->IsItalic()) {
             // For this we always use the font_matrix for uniformity. Not the
@@ -145,17 +145,19 @@ gfxDWriteFont::gfxDWriteFont(gfxFontEntr
     if (NS_FAILED(rv)) {
         mIsValid = PR_FALSE;
         return;
     }
 
     if ((anAAOption == gfxFont::kAntialiasDefault && UsingClearType()) ||
         anAAOption == gfxFont::kAntialiasSubpixel)
     {
-        mUsingClearType = PR_TRUE;
+        mUseSubpixelPositions = PR_TRUE;
+        // note that this may be reset to FALSE if we determine that a bitmap
+        // strike is going to be used
     }
 
     ComputeMetrics();
 
     if (FontCanSupportHarfBuzz()) {
         mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
     }
 }
@@ -204,16 +206,21 @@ gfxDWriteFont::ComputeMetrics()
     if (mStyle.sizeAdjust != 0.0) {
         gfxFloat aspect = (gfxFloat)fontMetrics.xHeight /
                    fontMetrics.designUnitsPerEm;
         mAdjustedSize = mStyle.GetAdjustedSize(aspect);
     } else {
         mAdjustedSize = mStyle.size;
     }
 
+    if (HasBitmapStrikeForSize(NS_lround(mAdjustedSize))) {
+        mAdjustedSize = NS_lround(mAdjustedSize);
+        mUseSubpixelPositions = PR_FALSE;
+    }
+
     mMetrics.xHeight =
         ((gfxFloat)fontMetrics.xHeight /
                    fontMetrics.designUnitsPerEm) * mAdjustedSize;
 
     mMetrics.maxAscent = 
         ceil(((gfxFloat)fontMetrics.ascent /
                    fontMetrics.designUnitsPerEm) * mAdjustedSize);
     mMetrics.maxDescent = 
@@ -336,16 +343,160 @@ gfxDWriteFont::ComputeMetrics()
     printf("    spaceWidth: %f aveCharWidth: %f zeroOrAve: %f xHeight: %f\n",
            mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.zeroOrAveCharWidth, mMetrics.xHeight);
     printf("    uOff: %f uSize: %f stOff: %f stSize: %f supOff: %f subOff: %f\n",
            mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize,
            mMetrics.superscriptOffset, mMetrics.subscriptOffset);
 #endif
 }
 
+using namespace mozilla; // for AutoSwap_* types
+
+struct EBLCHeader {
+    AutoSwap_PRUint32 version;
+    AutoSwap_PRUint32 numSizes;
+};
+
+struct SbitLineMetrics {
+    PRInt8  ascender;
+    PRInt8  descender;
+    PRUint8 widthMax;
+    PRInt8  caretSlopeNumerator;
+    PRInt8  caretSlopeDenominator;
+    PRInt8  caretOffset;
+    PRInt8  minOriginSB;
+    PRInt8  minAdvanceSB;
+    PRInt8  maxBeforeBL;
+    PRInt8  minAfterBL;
+    PRInt8  pad1;
+    PRInt8  pad2;
+};
+
+struct BitmapSizeTable {
+    AutoSwap_PRUint32 indexSubTableArrayOffset;
+    AutoSwap_PRUint32 indexTablesSize;
+    AutoSwap_PRUint32 numberOfIndexSubTables;
+    AutoSwap_PRUint32 colorRef;
+    SbitLineMetrics   hori;
+    SbitLineMetrics   vert;
+    AutoSwap_PRUint16 startGlyphIndex;
+    AutoSwap_PRUint16 endGlyphIndex;
+    PRUint8           ppemX;
+    PRUint8           ppemY;
+    PRUint8           bitDepth;
+    PRUint8           flags;
+};
+
+typedef EBLCHeader EBSCHeader;
+
+struct BitmapScaleTable {
+    SbitLineMetrics   hori;
+    SbitLineMetrics   vert;
+    PRUint8           ppemX;
+    PRUint8           ppemY;
+    PRUint8           substitutePpemX;
+    PRUint8           substitutePpemY;
+};
+
+PRBool
+gfxDWriteFont::HasBitmapStrikeForSize(PRUint32 aSize)
+{
+    PRUint8 *tableData;
+    PRUint32 len;
+    void *tableContext;
+    BOOL exists;
+    HRESULT hr =
+        mFontFace->TryGetFontTable(DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'L', 'C'),
+                                   (const void**)&tableData, &len,
+                                   &tableContext, &exists);
+    if (FAILED(hr)) {
+        return PR_FALSE;
+    }
+
+    PRBool hasStrike = PR_FALSE;
+    // not really a loop, but this lets us use 'break' to skip out of the block
+    // as soon as we know the answer, and skips it altogether if the table is
+    // not present
+    while (exists) {
+        if (len < sizeof(EBLCHeader)) {
+            break;
+        }
+        const EBLCHeader *hdr = reinterpret_cast<const EBLCHeader*>(tableData);
+        if (hdr->version != 0x00020000) {
+            break;
+        }
+        PRUint32 numSizes = hdr->numSizes;
+        if (numSizes > 0xffff) { // sanity-check, prevent overflow below
+            break;
+        }
+        if (len < sizeof(EBLCHeader) + numSizes * sizeof(BitmapSizeTable)) {
+            break;
+        }
+        const BitmapSizeTable *sizeTable =
+            reinterpret_cast<const BitmapSizeTable*>(hdr + 1);
+        for (PRUint32 i = 0; i < numSizes; ++i, ++sizeTable) {
+            if (sizeTable->ppemX == aSize && sizeTable->ppemY == aSize) {
+                // we ignore a strike that contains fewer than 4 glyphs,
+                // as that probably indicates a font such as Courier New
+                // that provides bitmaps ONLY for the "shading" characters
+                // U+2591..2593
+                hasStrike = (PRUint16(sizeTable->endGlyphIndex) >=
+                             PRUint16(sizeTable->startGlyphIndex) + 3);
+                break;
+            }
+        }
+        // if we reach here, we didn't find a strike; unconditionally break
+        // out of the while-loop block
+        break;
+    }
+    mFontFace->ReleaseFontTable(tableContext);
+
+    if (hasStrike) {
+        return PR_TRUE;
+    }
+
+    // if we didn't find a real strike, check if the font calls for scaling
+    // another bitmap to this size
+    hr = mFontFace->TryGetFontTable(DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'S', 'C'),
+                                    (const void**)&tableData, &len,
+                                    &tableContext, &exists);
+    if (FAILED(hr)) {
+        return PR_FALSE;
+    }
+
+    while (exists) {
+        if (len < sizeof(EBSCHeader)) {
+            break;
+        }
+        const EBSCHeader *hdr = reinterpret_cast<const EBSCHeader*>(tableData);
+        if (hdr->version != 0x00020000) {
+            break;
+        }
+        PRUint32 numSizes = hdr->numSizes;
+        if (numSizes > 0xffff) {
+            break;
+        }
+        if (len < sizeof(EBSCHeader) + numSizes * sizeof(BitmapScaleTable)) {
+            break;
+        }
+        const BitmapScaleTable *scaleTable =
+            reinterpret_cast<const BitmapScaleTable*>(hdr + 1);
+        for (PRUint32 i = 0; i < numSizes; ++i, ++scaleTable) {
+            if (scaleTable->ppemX == aSize && scaleTable->ppemY == aSize) {
+                hasStrike = PR_TRUE;
+                break;
+            }
+        }
+        break;
+    }
+    mFontFace->ReleaseFontTable(tableContext);
+
+    return hasStrike;
+}
+
 PRUint32
 gfxDWriteFont::GetSpaceGlyph()
 {
     UINT32 ucs = L' ';
     UINT16 glyph;
     HRESULT hr;
     hr = mFontFace->GetGlyphIndicesA(&ucs, 1, &glyph);
     if (FAILED(hr)) {
@@ -489,17 +640,17 @@ gfxDWriteFont::GetGlyphWidth(gfxContext 
 
     PRInt32 width = -1;
     if (mGlyphWidths.Get(aGID, &width)) {
         return width;
     }
 
     DWRITE_GLYPH_METRICS glyphMetrics;
     HRESULT hr;
-    if (mUsingClearType) {
+    if (mUseSubpixelPositions) {
         hr = mFontFace->GetDesignGlyphMetrics(
                   &aGID, 1, &glyphMetrics, FALSE);
         if (SUCCEEDED(hr)) {
             width =
                 NS_lround(glyphMetrics.advanceWidth * mFUnitsConvFactor *
                           65536.0);
         }
     } else {
--- a/gfx/thebes/gfxDWriteFonts.h
+++ b/gfx/thebes/gfxDWriteFonts.h
@@ -75,29 +75,31 @@ public:
 
     IDWriteFontFace *GetFontFace() { return mFontFace.get(); }
 
     // override gfxFont table access function to bypass gfxFontEntry cache,
     // use DWrite API to get direct access to system font data
     virtual hb_blob_t *GetFontTable(PRUint32 aTag);
 
     virtual PRBool ProvidesGlyphWidths() const {
-        return !mUsingClearType ||
+        return !mUseSubpixelPositions ||
                (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD);
     }
 
     virtual PRInt32 GetGlyphWidth(gfxContext *aCtx, PRUint16 aGID);
 
 protected:
     friend class gfxDWriteShaper;
 
     virtual void CreatePlatformShaper();
 
     void ComputeMetrics();
 
+    PRBool HasBitmapStrikeForSize(PRUint32 aSize);
+
     cairo_font_face_t *CairoFontFace();
 
     cairo_scaled_font_t *CairoScaledFont();
 
     static void DestroyBlobFunc(void* userArg);
 
     nsRefPtr<IDWriteFontFace> mFontFace;
     cairo_font_face_t *mCairoFontFace;
@@ -105,12 +107,12 @@ protected:
 
     gfxFont::Metrics mMetrics;
 
     // cache of glyph widths in 16.16 fixed-point pixels
     nsDataHashtable<nsUint32HashKey,PRInt32>    mGlyphWidths;
 
     PRPackedBool mNeedsOblique;
     PRPackedBool mNeedsBold;
-    PRPackedBool mUsingClearType;
+    PRPackedBool mUseSubpixelPositions;
 };
 
 #endif
--- a/gfx/thebes/gfxDWriteShaper.cpp
+++ b/gfx/thebes/gfxDWriteShaper.cpp
@@ -170,17 +170,17 @@ trymoreglyphs:
         WORD gID = indices[0];
         nsAutoTArray<FLOAT, 400> advances;
         nsAutoTArray<DWRITE_GLYPH_OFFSET, 400> glyphOffsets;
         if (!advances.SetLength(actualGlyphs) || 
             !glyphOffsets.SetLength(actualGlyphs)) {
             continue;
         }
 
-        if (!static_cast<gfxDWriteFont*>(mFont)->mUsingClearType) {
+        if (!static_cast<gfxDWriteFont*>(mFont)->mUseSubpixelPositions) {
             hr = analyzer->GetGdiCompatibleGlyphPlacements(
                                               aString + range.start,
                                               clusters.Elements(),
                                               textProperties.Elements(),
                                               range.Length(),
                                               indices.Elements(),
                                               glyphProperties.Elements(),
                                               actualGlyphs,