bug 674909 - make synthetic bolding proportionate to font size and zoom. r=jdaggett
authorJonathan Kew <jfkthame@gmail.com>
Fri, 02 Sep 2011 21:18:25 +0100
changeset 76484 5b34c14d9f842c1357abe0882cc61ffa8a2de7b5
parent 76483 c1281875cfea5d0dc1405ddd089d63ffcb4ebad6
child 76485 e1d7cac1fa83d6d3e008171dcdf019ad3dd21d42
child 76489 3326454d70f5116055e1f32df8d733524c3ff04a
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersjdaggett
bugs674909
milestone9.0a1
bug 674909 - make synthetic bolding proportionate to font size and zoom. r=jdaggett
gfx/thebes/gfxFT2Fonts.cpp
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxMacFont.cpp
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -695,17 +695,17 @@ gfxFT2Font::InitTextRun(gfxContext *aCon
         ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString,
                                           aRunStart, aRunLength, aRunScript);
     }
 
     if (!ok) {
         AddRange(aTextRun, aString, aRunStart, aRunLength);
     }
 
-    aTextRun->AdjustAdvancesForSyntheticBold(aRunStart, aRunLength);
+    aTextRun->AdjustAdvancesForSyntheticBold(aContext, aRunStart, aRunLength);
 
     return PR_TRUE;
 }
 
 void
 gfxFT2Font::AddRange(gfxTextRun *aTextRun, const PRUnichar *str, PRUint32 offset, PRUint32 len)
 {
     const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
@@ -805,19 +805,17 @@ gfxFT2Font::AddRange(gfxTextRun *aTextRu
 
 gfxFT2Font::gfxFT2Font(cairo_scaled_font_t *aCairoFont,
                        FontEntry *aFontEntry,
                        const gfxFontStyle *aFontStyle,
                        PRBool aNeedsBold)
     : gfxFT2FontBase(aCairoFont, aFontEntry, aFontStyle)
 {
     NS_ASSERTION(mFontEntry, "Unable to find font entry for font.  Something is whack.");
-    if (aNeedsBold) {
-        mSyntheticBoldOffset = 1.0;
-    }
+    mApplySyntheticBold = aNeedsBold;
     mCharGlyphCache.Init(64);
 }
 
 gfxFT2Font::~gfxFT2Font()
 {
 }
 
 cairo_font_face_t *
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1030,20 +1030,20 @@ gfxFont::RunMetrics::CombineWith(const R
             mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0));
     }
     mAdvanceWidth += aOther.mAdvanceWidth;
 }
 
 gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
                  AntialiasOption anAAOption) :
     mFontEntry(aFontEntry), mIsValid(PR_TRUE),
+    mApplySyntheticBold(PR_FALSE),
     mStyle(*aFontStyle),
     mAdjustedSize(0.0),
     mFUnitsConvFactor(0.0f),
-    mSyntheticBoldOffset(0),
     mAntialiasOption(anAAOption),
     mPlatformShaper(nsnull),
     mHarfBuzzShaper(nsnull)
 {
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
     ++gFontCount;
 #endif
 }
@@ -1110,32 +1110,68 @@ struct GlyphBuffer {
         else
             cairo_show_glyphs(aCR, mGlyphBuffer, mNumGlyphs);
 
         mNumGlyphs = 0;
     }
 #undef GLYPH_BUFFER_SIZE
 };
 
+// Bug 674909. When synthetic bolding text by drawing twice, need to
+// render using a pixel offset in device pixels, otherwise text
+// doesn't appear bolded, it appears as if a bad text shadow exists
+// when a non-identity transform exists.  Use an offset factor so that
+// the second draw occurs at a constant offset in device pixels.
+
+static double
+CalcXScale(gfxContext *aContext)
+{
+    // determine magnitude of a 1px x offset in device space
+    gfxSize t = aContext->UserToDevice(gfxSize(1.0, 0.0));
+    if (t.width == 1.0 && t.height == 0.0) {
+        // short-circuit the most common case to avoid sqrt() and division
+        return 1.0;
+    }
+
+    double m = sqrt(t.width * t.width + t.height * t.height);
+
+    NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
+    if (m == 0.0) {
+        return 0.0; // effectively disables offset
+    }
+
+    // scale factor so that offsets are 1px in device pixels
+    return 1.0 / m;
+}
+
 void
 gfxFont::Draw(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd,
               gfxContext *aContext, PRBool aDrawToPath, gfxPoint *aPt,
               Spacing *aSpacing)
 {
     if (aStart >= aEnd)
         return;
 
     const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
     const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
     PRBool isRTL = aTextRun->IsRightToLeft();
     double direction = aTextRun->GetDirection();
-    // double-strike in direction of run
-    double synBoldDevUnitOffsetAppUnits =
-      direction * (double) mSyntheticBoldOffset * appUnitsPerDevUnit;
+
+    // synthetic-bold strikes are each offset one device pixel in run direction
+    // (these values are only needed if IsSyntheticBold() is true)
+    double synBoldOnePixelOffset;
+    PRInt32 strikes;
+    if (IsSyntheticBold()) {
+        double xscale = CalcXScale(aContext);
+        synBoldOnePixelOffset = direction * xscale;
+        // use as many strikes as needed for the the increased advance
+        strikes = NS_lroundf(GetSyntheticBoldOffset() / xscale);
+    }
+
     PRUint32 i;
     // Current position in appunits
     double x = aPt->x;
     double y = aPt->y;
 
     PRBool success = SetupCairoFont(aContext);
     if (NS_UNLIKELY(!success))
         return;
@@ -1164,25 +1200,31 @@ gfxFont::Draw(gfxTextRun *aTextRun, PRUi
                 glyphX = x;
             } else {
                 glyphX = x;
                 x += advance;
             }
             glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
             glyph->y = ToDeviceUnits(y, devUnitsPerAppUnit);
             
-            // synthetic bolding by drawing with a one-pixel offset
-            if (mSyntheticBoldOffset) {
-                cairo_glyph_t *doubleglyph;
-                doubleglyph = glyphs.AppendGlyph();
-                doubleglyph->index = glyph->index;
-                doubleglyph->x =
-                  ToDeviceUnits(glyphX + synBoldDevUnitOffsetAppUnits,
-                                devUnitsPerAppUnit);
-                doubleglyph->y = glyph->y;
+            // synthetic bolding by multi-striking with 1-pixel offsets
+            // at least once, more if there's room (large font sizes)
+            if (IsSyntheticBold()) {
+                double strikeOffset = synBoldOnePixelOffset;
+                PRInt32 strikeCount = strikes;
+                do {
+                    cairo_glyph_t *doubleglyph;
+                    doubleglyph = glyphs.AppendGlyph();
+                    doubleglyph->index = glyph->index;
+                    doubleglyph->x =
+                        ToDeviceUnits(glyphX + strikeOffset * appUnitsPerDevUnit,
+                                      devUnitsPerAppUnit);
+                    doubleglyph->y = glyph->y;
+                    strikeOffset += synBoldOnePixelOffset;
+                } while (--strikeCount > 0);
             }
             
             glyphs.Flush(cr, aDrawToPath, isRTL);
         } else {
             PRUint32 glyphCount = glyphData->GetGlyphCount();
             if (glyphCount > 0) {
                 const gfxTextRun::DetailedGlyph *details =
                     aTextRun->GetDetailedGlyphs(i);
@@ -1211,25 +1253,30 @@ gfxFont::Draw(gfxTextRun *aTextRun, PRUi
                         glyph->index = details->mGlyphID;
                         double glyphX = x + details->mXOffset;
                         if (isRTL) {
                             glyphX -= advance;
                         }
                         glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
                         glyph->y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit);
 
-                        // synthetic bolding by drawing with a one-pixel offset
-                        if (mSyntheticBoldOffset) {
-                            cairo_glyph_t *doubleglyph;
-                            doubleglyph = glyphs.AppendGlyph();
-                            doubleglyph->index = glyph->index;
-                            doubleglyph->x =
-                                ToDeviceUnits(glyphX + synBoldDevUnitOffsetAppUnits,
-                                              devUnitsPerAppUnit);
-                            doubleglyph->y = glyph->y;
+                        if (IsSyntheticBold()) {
+                            double strikeOffset = synBoldOnePixelOffset;
+                            PRInt32 strikeCount = strikes;
+                            do {
+                                cairo_glyph_t *doubleglyph;
+                                doubleglyph = glyphs.AppendGlyph();
+                                doubleglyph->index = glyph->index;
+                                doubleglyph->x =
+                                    ToDeviceUnits(glyphX + strikeOffset *
+                                                      appUnitsPerDevUnit,
+                                                  devUnitsPerAppUnit);
+                                doubleglyph->y = glyph->y;
+                                strikeOffset += synBoldOnePixelOffset;
+                            } while (--strikeCount > 0);
                         }
 
                         glyphs.Flush(cr, aDrawToPath, isRTL);
                     }
                     x += direction*advance;
                 }
             }
         }
@@ -3487,26 +3534,30 @@ struct BufferAlphaColor {
         mContext->Restore();
     }
 
     gfxContext *mContext;
     gfxFloat mAlpha;
 };
 
 void
-gfxTextRun::AdjustAdvancesForSyntheticBold(PRUint32 aStart, PRUint32 aLength)
+gfxTextRun::AdjustAdvancesForSyntheticBold(gfxContext *aContext,
+                                           PRUint32 aStart,
+                                           PRUint32 aLength)
 {
     const PRUint32 appUnitsPerDevUnit = GetAppUnitsPerDevUnit();
     PRBool isRTL = IsRightToLeft();
 
     GlyphRunIterator iter(this, aStart, aLength);
     while (iter.NextRun()) {
         gfxFont *font = iter.GetGlyphRun()->mFont;
         if (font->IsSyntheticBold()) {
-            PRUint32 synAppUnitOffset = font->GetSyntheticBoldOffset() * appUnitsPerDevUnit;
+            PRUint32 synAppUnitOffset =
+                font->GetSyntheticBoldOffset() *
+                    appUnitsPerDevUnit * CalcXScale(aContext);
             PRUint32 start = iter.GetStringStart();
             PRUint32 end = iter.GetStringEnd();
             PRUint32 i;
             
             // iterate over glyphs, start to end
             for (i = start; i < end; ++i) {
                 gfxTextRun::CompressedGlyph *glyphData = &mCharacterGlyphs[i];
                 
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -1188,18 +1188,22 @@ public:
 
     // You need to call SetupCairoFont on the aCR just before calling this
     virtual void SetupGlyphExtents(gfxContext *aContext, PRUint32 aGlyphID,
                                    PRBool aNeedTight, gfxGlyphExtents *aExtents);
 
     // This is called by the default Draw() implementation above.
     virtual PRBool SetupCairoFont(gfxContext *aContext) = 0;
 
-    PRBool IsSyntheticBold() { return mSyntheticBoldOffset != 0; }
-    PRUint32 GetSyntheticBoldOffset() { return mSyntheticBoldOffset; }
+    PRBool IsSyntheticBold() { return mApplySyntheticBold; }
+
+    // Amount by which synthetic bold "fattens" the glyphs: 1/16 of the em-size
+    gfxFloat GetSyntheticBoldOffset() {
+        return GetAdjustedSize() * (1.0 / 16.0);
+    }
 
     gfxFontEntry *GetFontEntry() { return mFontEntry.get(); }
     PRBool HasCharacter(PRUint32 ch) {
         if (!mIsValid)
             return PR_FALSE;
         return mFontEntry->HasCharacter(ch); 
     }
 
@@ -1219,27 +1223,29 @@ public:
                                PRUint32 aRunStart,
                                PRUint32 aRunLength,
                                PRInt32 aRunScript);
 
 protected:
     nsRefPtr<gfxFontEntry> mFontEntry;
 
     PRPackedBool               mIsValid;
+
+    // use synthetic bolding for environments where this is not supported
+    // by the platform
+    PRPackedBool               mApplySyntheticBold;
+
     nsExpirationState          mExpirationState;
     gfxFontStyle               mStyle;
     nsAutoTArray<gfxGlyphExtents*,1> mGlyphExtentsArray;
 
     gfxFloat                   mAdjustedSize;
 
     float                      mFUnitsConvFactor; // conversion factor from font units to dev units
 
-    // synthetic bolding for environments where this is not supported by the platform
-    PRUint32                   mSyntheticBoldOffset;  // number of devunit pixels to offset double-strike, 0 ==> no bolding
-
     // the AA setting requested for this font - may affect glyph bounds
     AntialiasOption            mAntialiasOption;
 
     // a copy of the font without antialiasing, if needed for separate
     // measurement by mathml code
     nsAutoPtr<gfxFont>         mNonAAFont;
 
     // we may switch between these shapers on the fly, based on the script
@@ -2038,17 +2044,19 @@ public:
     // generation of gfxTextRunWordCache that refers to this textrun;
     // if the cache gets cleared, then mCachedWords is no longer meaningful
     PRUint32 mCacheGeneration;
     
     void Dump(FILE* aOutput);
 #endif
 
     // post-process glyph advances to deal with synthetic bolding
-    void AdjustAdvancesForSyntheticBold(PRUint32 aStart, PRUint32 aLength);
+    void AdjustAdvancesForSyntheticBold(gfxContext *aContext,
+                                        PRUint32 aStart,
+                                        PRUint32 aLength);
 
 protected:
     /**
      * Initializes the textrun to blank.
      * @param aGlyphStorage preallocated array of CompressedGlyph[aLength]
      * for the textrun to use; if aText is not persistent, then it has also
      * been appended to this array, so it must NOT be freed separately.
      */
--- a/gfx/thebes/gfxMacFont.cpp
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -52,19 +52,17 @@ using namespace mozilla;
 
 gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
                        PRBool aNeedsBold)
     : gfxFont(aFontEntry, aFontStyle),
       mCGFont(nsnull),
       mFontFace(nsnull),
       mScaledFont(nsnull)
 {
-    if (aNeedsBold) {
-        mSyntheticBoldOffset = 1;  // devunit offset when double-striking text to fake boldness
-    }
+    mApplySyntheticBold = aNeedsBold;
 
     mCGFont = aFontEntry->GetFontRef();
     if (!mCGFont) {
         mIsValid = PR_FALSE;
         return;
     }
 
     // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize
@@ -162,17 +160,17 @@ gfxMacFont::InitTextRun(gfxContext *aCon
         NS_WARNING("invalid font! expect incorrect text rendering");
         return PR_FALSE;
     }
 
     PRBool ok = gfxFont::InitTextRun(aContext, aTextRun, aString,
                                      aRunStart, aRunLength, aRunScript,
         static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout());
 
-    aTextRun->AdjustAdvancesForSyntheticBold(aRunStart, aRunLength);
+    aTextRun->AdjustAdvancesForSyntheticBold(aContext, aRunStart, aRunLength);
 
     return ok;
 }
 
 void
 gfxMacFont::CreatePlatformShaper()
 {
     mPlatformShaper = new gfxCoreTextShaper(this);
@@ -315,18 +313,20 @@ gfxMacFont::InitMetrics()
     if (mMetrics.aveCharWidth <= 0) {
         mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID,
                                              cgConvFactor);
         if (glyphID == 0) {
             // we didn't find 'x', so use maxAdvance rather than zero
             mMetrics.aveCharWidth = mMetrics.maxAdvance;
         }
     }
-    mMetrics.aveCharWidth += mSyntheticBoldOffset;
-    mMetrics.maxAdvance += mSyntheticBoldOffset;
+    if (IsSyntheticBold()) {
+        mMetrics.aveCharWidth += GetSyntheticBoldOffset();
+        mMetrics.maxAdvance += GetSyntheticBoldOffset();
+    }
 
     mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor);
     if (glyphID == 0) {
         // no space glyph?!
         mMetrics.spaceWidth = mMetrics.aveCharWidth;
     }
     mSpaceGlyph = glyphID;