bug 532533 - Avoid flaky Apple font metrics API if possible, by reading metrics directly from font tables.

diff --git a/gfx/thebes/public/gfxFont.h b/gfx/thebes/public/gfxFont.h
--- a/gfx/thebes/public/gfxFont.h
+++ b/gfx/thebes/public/gfxFont.h
@@ -213,16 +213,20 @@ public:
 
     virtual PRBool MatchesGenericFamily(const nsACString& aGeneric) const {
         return PR_TRUE;
     }
     virtual PRBool SupportsLangGroup(nsIAtom *aLangGroup) const {
         return PR_TRUE;
     }
 
+    virtual nsresult GetFontTable(PRUint32 aTableTag, nsTArray<PRUint8>& aBuffer) {
+        return NS_ERROR_FAILURE; // all platform subclasses should reimplement this!
+    }
+
     void SetFamily(gfxFontFamily* aFamily) {
         mFamily = aFamily;
     }
 
     const nsString& FamilyName();
 
     already_AddRefed<gfxFont> FindOrMakeFont(const gfxFontStyle *aStyle, PRBool aNeedsBold);
 
@@ -259,20 +263,16 @@ protected:
         mStandardFace(PR_FALSE),
         mSymbolFont(PR_FALSE),
         mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
         mCmapInitialized(PR_FALSE),
         mUserFontData(nsnull),
         mFamily(nsnull)
     { }
 
-    virtual nsresult GetFontTable(PRUint32 aTableTag, nsTArray<PRUint8>& aBuffer) {
-        return NS_ERROR_FAILURE; // all platform subclasses should reimplement this!
-    }
-
     virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, PRBool aNeedsBold) {
         NS_NOTREACHED("oops, somebody didn't override CreateFontInstance");
         return nsnull;
     }
 
     gfxFontFamily *mFamily;
 };
 
@@ -945,21 +945,32 @@ protected:
     // 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;
 
     nsAutoPtr<gfxFontShaper>   mShaper;
 
+    // Helper for subclasses that want to initialize standard metrics from the
+    // tables of sfnt (TrueType/OpenType) fonts.
+    PRBool InitMetricsFromSfntTables(Metrics& aMetrics, gfxFloat aSize);
+
+    // Helper to calculate various derived metrics from the results of
+    // InitMetricsFromSfntTables or equivalent platform code
+    void CalculateDerivedMetrics(Metrics& aMetrics);
+
     // some fonts have bad metrics, this method sanitize them.
     // if this font has bad underline offset, aIsBadUnderlineFont should be true.
     void SanitizeMetrics(gfxFont::Metrics *aMetrics, PRBool aIsBadUnderlineFont);
 };
 
+// proportion of ascent used for x-height, if unable to read value from font
+#define DEFAULT_XHEIGHT_FACTOR 0.56f
+
 class THEBES_API gfxTextRunFactory {
     NS_INLINE_DECL_REFCOUNTING(gfxTextRunFactory)
 
 public:
     // Flags in the mask 0xFFFF0000 are reserved for textrun clients
     // Flags in the mask 0x0000F000 are reserved for per-platform fonts
     // Flags in the mask 0x00000FFF are set by the textrun creator.
     enum {
diff --git a/gfx/thebes/public/gfxFontUtils.h b/gfx/thebes/public/gfxFontUtils.h
--- a/gfx/thebes/public/gfxFontUtils.h
+++ b/gfx/thebes/public/gfxFontUtils.h
@@ -334,27 +334,135 @@ struct AutoSwap_PRUint32 {
       { this->value = NS_SWAP32(aValue); return *this; }
 #else
     AutoSwap_PRUint32(PRUint32 aValue) { value = NS_SWAP32(aValue); }
 #endif
     operator PRUint32() const { return NS_SWAP32(value); }
     PRUint32  value;
 };
 
+struct AutoSwap_PRInt32 {
+#ifdef __SUNPRO_CC
+    AutoSwap_PRInt32& operator = (const PRInt32 aValue)
+      { this->value = NS_SWAP32(aValue); return *this; }
+#else
+    AutoSwap_PRInt32(PRInt32 aValue) { value = NS_SWAP32(aValue); }
+#endif
+    operator PRInt32() const { return NS_SWAP32(value); }
+    PRInt32  value;
+};
+
 struct AutoSwap_PRUint64 {
 #ifdef __SUNPRO_CC
     AutoSwap_PRUint64& operator = (const PRUint64 aValue)
       { this->value = NS_SWAP64(aValue); return *this; }
 #else
     AutoSwap_PRUint64(PRUint64 aValue) { value = NS_SWAP64(aValue); }
 #endif
     operator PRUint64() const { return NS_SWAP64(value); }
     PRUint64  value;
 };
 
+struct HeadTable {
+    enum {
+        HEAD_MAGIC_NUMBER = 0x5F0F3CF5,
+        HEAD_CHECKSUM_CALC_CONST = 0xB1B0AFBA
+    };
+
+    AutoSwap_PRUint32    tableVersionNumber;    // Fixed, 0x00010000 for version 1.0.
+    AutoSwap_PRUint32    fontRevision;          // Set by font manufacturer.
+    AutoSwap_PRUint32    checkSumAdjustment;    // To compute: set it to 0, sum the entire font as ULONG, then store 0xB1B0AFBA - sum.
+    AutoSwap_PRUint32    magicNumber;           // Set to 0x5F0F3CF5.
+    AutoSwap_PRUint16    flags;
+    AutoSwap_PRUint16    unitsPerEm;            // Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines.
+    AutoSwap_PRUint64    created;               // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
+    AutoSwap_PRUint64    modified;              // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
+    AutoSwap_PRInt16     xMin;                  // For all glyph bounding boxes.
+    AutoSwap_PRInt16     yMin;                  // For all glyph bounding boxes.
+    AutoSwap_PRInt16     xMax;                  // For all glyph bounding boxes.
+    AutoSwap_PRInt16     yMax;                  // For all glyph bounding boxes.
+    AutoSwap_PRUint16    macStyle;              // Bit 0: Bold (if set to 1);
+    AutoSwap_PRUint16    lowestRecPPEM;         // Smallest readable size in pixels.
+    AutoSwap_PRInt16     fontDirectionHint;
+    AutoSwap_PRInt16     indexToLocFormat;
+    AutoSwap_PRInt16     glyphDataFormat;
+};
+
+struct OS2Table {
+    AutoSwap_PRUint16    version;                // 0004 = OpenType 1.5
+    AutoSwap_PRInt16     xAvgCharWidth;
+    AutoSwap_PRUint16    usWeightClass;
+    AutoSwap_PRUint16    usWidthClass;
+    AutoSwap_PRUint16    fsType;
+    AutoSwap_PRInt16     ySubscriptXSize;
+    AutoSwap_PRInt16     ySubscriptYSize;
+    AutoSwap_PRInt16     ySubscriptXOffset;
+    AutoSwap_PRInt16     ySubscriptYOffset;
+    AutoSwap_PRInt16     ySuperscriptXSize;
+    AutoSwap_PRInt16     ySuperscriptYSize;
+    AutoSwap_PRInt16     ySuperscriptXOffset;
+    AutoSwap_PRInt16     ySuperscriptYOffset;
+    AutoSwap_PRInt16     yStrikeoutSize;
+    AutoSwap_PRInt16     yStrikeoutPosition;
+    AutoSwap_PRInt16     sFamilyClass;
+    PRUint8              panose[10];
+    AutoSwap_PRUint32    unicodeRange1;
+    AutoSwap_PRUint32    unicodeRange2;
+    AutoSwap_PRUint32    unicodeRange3;
+    AutoSwap_PRUint32    unicodeRange4;
+    PRUint8              achVendID[4];
+    AutoSwap_PRUint16    fsSelection;
+    AutoSwap_PRUint16    usFirstCharIndex;
+    AutoSwap_PRUint16    usLastCharIndex;
+    AutoSwap_PRInt16     sTypoAscender;
+    AutoSwap_PRInt16     sTypoDescender;
+    AutoSwap_PRInt16     sTypoLineGap;
+    AutoSwap_PRUint16    usWinAscent;
+    AutoSwap_PRUint16    usWinDescent;
+    AutoSwap_PRUint32    codePageRange1;
+    AutoSwap_PRUint32    codePageRange2;
+    AutoSwap_PRInt16     sxHeight;
+    AutoSwap_PRInt16     sCapHeight;
+    AutoSwap_PRUint16    usDefaultChar;
+    AutoSwap_PRUint16    usBreakChar;
+    AutoSwap_PRUint16    usMaxContext;
+};
+
+struct PostTable {
+    AutoSwap_PRUint32    version;
+    AutoSwap_PRInt32     italicAngle;
+    AutoSwap_PRInt16     underlinePosition;
+    AutoSwap_PRUint16    underlineThickness;
+    AutoSwap_PRUint32    isFixedPitch;
+    AutoSwap_PRUint32    minMemType42;
+    AutoSwap_PRUint32    maxMemType42;
+    AutoSwap_PRUint32    minMemType1;
+    AutoSwap_PRUint32    maxMemType1;
+};
+
+struct HheaTable {
+    AutoSwap_PRUint32    version;
+    AutoSwap_PRInt16     ascender;
+    AutoSwap_PRInt16     descender;
+    AutoSwap_PRInt16     lineGap;
+    AutoSwap_PRUint16    advanceWidthMax;
+    AutoSwap_PRInt16     minLeftSideBearing;
+    AutoSwap_PRInt16     minRightSideBearing;
+    AutoSwap_PRInt16     xMaxExtent;
+    AutoSwap_PRInt16     caretSlopeRise;
+    AutoSwap_PRInt16     caretSlopeRun;
+    AutoSwap_PRInt16     caretOffset;
+    AutoSwap_PRInt16     reserved1;
+    AutoSwap_PRInt16     reserved2;
+    AutoSwap_PRInt16     reserved3;
+    AutoSwap_PRInt16     reserved4;
+    AutoSwap_PRInt16     metricDataFormat;
+    AutoSwap_PRUint16    numOfLongHorMetrics;
+};
+
 #pragma pack()
 
 } // namespace mozilla
 
 // used for overlaying name changes without touching original font data
 struct FontDataOverlay {
     // overlaySrc != 0 ==> use overlay
     PRUint32  overlaySrc;    // src offset from start of font data
diff --git a/gfx/thebes/src/gfxFont.cpp b/gfx/thebes/src/gfxFont.cpp
--- a/gfx/thebes/src/gfxFont.cpp
+++ b/gfx/thebes/src/gfxFont.cpp
@@ -1257,16 +1257,160 @@ gfxFont::SetupGlyphExtents(gfxContext *a
 #endif
 
     double d2a = appUnitsPerDevUnit;
     gfxRect bounds(extents.x_bearing*d2a, extents.y_bearing*d2a,
                    extents.width*d2a, extents.height*d2a);
     aExtents->SetTightGlyphExtents(aGlyphID, bounds);
 }
 
+// Try to initialize font metrics by reading sfnt tables directly;
+// return false if not able to get all the necessary tables.
+// (This will always return false if the gfxFontEntry subclass does not
+// implement GetFontTable(), or for non-sfnt fonts where tables are
+// not available.)
+PRBool
+gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics, gfxFloat aSize)
+{
+    const PRUint32 kHeadTableTag = TRUETYPE_TAG('h','e','a','d');
+    const PRUint32 kHheaTableTag = TRUETYPE_TAG('h','h','e','a');
+    const PRUint32 kPostTableTag = TRUETYPE_TAG('p','o','s','t');
+    const PRUint32 kOS_2TableTag = TRUETYPE_TAG('O','S','/','2');
+
+    // 'head' table is required, otherwise we cannot read any metrics
+    // because we don't know unitsPerEm
+    nsAutoTArray<PRUint8,sizeof(mozilla::HeadTable)> headData;
+    if (NS_FAILED(mFontEntry->GetFontTable(kHeadTableTag, headData)) ||
+        headData.Length() < sizeof(mozilla::HeadTable)) {
+        return PR_FALSE;
+    }
+    mozilla::HeadTable *head =
+        reinterpret_cast<mozilla::HeadTable*>(headData.Elements());
+    PRUint16 upem = head->unitsPerEm;
+    if (!upem) {
+        return PR_FALSE;
+    }
+
+    gfxFloat scale = aSize / upem;
+
+    // 'hhea' table is required to get vertical extents
+    nsAutoTArray<PRUint8,sizeof(mozilla::HheaTable)> hheaData;
+    if (NS_FAILED(mFontEntry->GetFontTable(kHheaTableTag, hheaData)) ||
+        hheaData.Length() < sizeof(mozilla::HheaTable)) {
+        return PR_FALSE;
+    }
+    mozilla::HheaTable *hhea =
+        reinterpret_cast<mozilla::HheaTable*>(hheaData.Elements());
+
+#define SET_UNSIGNED(field,src) aMetrics.field = PRUint16(src) * scale
+#define SET_SIGNED(field,src)   aMetrics.field = PRInt16(src) * scale
+
+    SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax);
+    SET_SIGNED(maxAscent, hhea->ascender);
+    SET_SIGNED(maxDescent, -PRInt16(hhea->descender));
+    SET_SIGNED(externalLeading, hhea->lineGap);
+
+    // 'post' table is required for underline metrics
+    nsAutoTArray<PRUint8,sizeof(mozilla::PostTable)> postData;
+    if (NS_FAILED(mFontEntry->GetFontTable(kPostTableTag, postData))) {
+        return PR_FALSE;
+    }
+    if (postData.Length() <
+        offsetof(mozilla::PostTable, underlineThickness) + sizeof(PRUint16)) {
+        return PR_FALSE;
+    }
+    mozilla::PostTable *post =
+        reinterpret_cast<mozilla::PostTable*>(postData.Elements());
+
+    SET_SIGNED(underlineOffset, post->underlinePosition);
+    SET_UNSIGNED(underlineSize, post->underlineThickness);
+
+    // 'OS/2' table is optional, if not found we'll estimate xHeight
+    // and aveCharWidth by measuring glyphs
+    nsAutoTArray<PRUint8,sizeof(mozilla::OS2Table)> os2data;
+    if (NS_SUCCEEDED(mFontEntry->GetFontTable(kOS_2TableTag, os2data))) {
+        mozilla::OS2Table *os2 =
+            reinterpret_cast<mozilla::OS2Table*>(os2data.Elements());
+
+        if (os2data.Length() >= offsetof(mozilla::OS2Table, sxHeight)
+                                    + sizeof(PRInt16) &&
+            PRUint16(os2->version) >= 2) {
+            // version 2 and later includes the x-height field
+            SET_SIGNED(xHeight, os2->sxHeight);
+            // PR_ABS because of negative xHeight seen in Kokonor (Tibetan) font
+            aMetrics.xHeight = PR_ABS(aMetrics.xHeight);
+        }
+        // this should always be present
+        if (os2data.Length() >= offsetof(mozilla::OS2Table, yStrikeoutPosition)
+                                    + sizeof(PRInt16)) {
+            SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
+            SET_SIGNED(subscriptOffset, os2->ySubscriptYOffset);
+            SET_SIGNED(superscriptOffset, os2->ySuperscriptYOffset);
+            SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
+            SET_SIGNED(strikeoutOffset, os2->yStrikeoutPosition);
+        }
+    }
+
+    return PR_TRUE;
+}
+
+static double
+RoundToNearestMultiple(double aValue, double aFraction)
+{
+    return floor(aValue/aFraction + 0.5) * aFraction;
+}
+
+void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics)
+{
+    aMetrics.maxAscent =
+        NS_ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1/1024.0));
+    aMetrics.maxDescent =
+        NS_ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1/1024.0));
+
+    if (aMetrics.xHeight <= 0) {
+        // only happens if we couldn't find either font metrics
+        // or a char to measure;
+        // pick an arbitrary value that's better than zero
+        aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR;
+    }
+
+    aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent;
+
+    if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) {
+        aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight;
+    } else {
+        aMetrics.internalLeading = 0.0;
+    }
+
+    aMetrics.emAscent = aMetrics.maxAscent * aMetrics.emHeight
+                            / aMetrics.maxHeight;
+    aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent;
+
+    if (GetFontEntry()->IsFixedPitch()) {
+        // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
+        // advance than the average character width... this forces
+        // those fonts to be recognized like fixed pitch fonts by layout.
+        aMetrics.maxAdvance = aMetrics.aveCharWidth;
+    }
+
+    if (!aMetrics.subscriptOffset) {
+        aMetrics.subscriptOffset = aMetrics.xHeight;
+    }
+    if (!aMetrics.superscriptOffset) {
+        aMetrics.superscriptOffset = aMetrics.xHeight;
+    }
+
+    if (!aMetrics.strikeoutOffset) {
+        aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5;
+    }
+    if (!aMetrics.strikeoutSize) {
+        aMetrics.strikeoutSize = aMetrics.underlineSize;
+    }
+}
+
 void
 gfxFont::SanitizeMetrics(gfxFont::Metrics *aMetrics, PRBool aIsBadUnderlineFont)
 {
     // Even if this font size is zero, this font is created with non-zero size.
     // However, for layout and others, we should return the metrics of zero size font.
     if (mStyle.size == 0) {
         memset(aMetrics, 0, sizeof(gfxFont::Metrics));
         return;
diff --git a/gfx/thebes/src/gfxFontUtils.cpp b/gfx/thebes/src/gfxFontUtils.cpp
--- a/gfx/thebes/src/gfxFontUtils.cpp
+++ b/gfx/thebes/src/gfxFontUtils.cpp
@@ -664,90 +664,25 @@ struct SFNTHeader {
 
 struct TableDirEntry {
     AutoSwap_PRUint32    tag;                    // 4 -byte identifier.
     AutoSwap_PRUint32    checkSum;               // CheckSum for this table.
     AutoSwap_PRUint32    offset;                 // Offset from beginning of TrueType font file.
     AutoSwap_PRUint32    length;                 // Length of this table.        
 };
 
-struct HeadTable {
-    enum {
-        HEAD_MAGIC_NUMBER = 0x5F0F3CF5,
-        HEAD_CHECKSUM_CALC_CONST = 0xB1B0AFBA
-    };
-
-    AutoSwap_PRUint32    tableVersionNumber;    // Fixed, 0x00010000 for version 1.0.
-    AutoSwap_PRUint32    fontRevision;          // Set by font manufacturer.
-    AutoSwap_PRUint32    checkSumAdjustment;    // To compute: set it to 0, sum the entire font as ULONG, then store 0xB1B0AFBA - sum.
-    AutoSwap_PRUint32    magicNumber;           // Set to 0x5F0F3CF5.
-    AutoSwap_PRUint16    flags;
-    AutoSwap_PRUint16    unitsPerEm;            // Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines.
-    AutoSwap_PRUint64    created;               // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
-    AutoSwap_PRUint64    modified;              // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
-    AutoSwap_PRInt16     xMin;                  // For all glyph bounding boxes.
-    AutoSwap_PRInt16     yMin;                  // For all glyph bounding boxes.
-    AutoSwap_PRInt16     xMax;                  // For all glyph bounding boxes.
-    AutoSwap_PRInt16     yMax;                  // For all glyph bounding boxes.
-    AutoSwap_PRUint16    macStyle;              // Bit 0: Bold (if set to 1);
-    AutoSwap_PRUint16    lowestRecPPEM;         // Smallest readable size in pixels.
-    AutoSwap_PRInt16     fontDirectionHint;
-    AutoSwap_PRInt16     indexToLocFormat;
-    AutoSwap_PRInt16     glyphDataFormat;
-};
-
 // name table stores set of name record structures, followed by
 // large block containing all the strings.  name record offset and length
 // indicates the offset and length within that block.
 // http://www.microsoft.com/typography/otspec/name.htm
 struct NameRecordData {
     PRUint32  offset;
     PRUint32  length;
 };
 
-struct OS2Table {
-    AutoSwap_PRUint16    version;                // 0004 = OpenType 1.5
-    AutoSwap_PRInt16     xAvgCharWidth;
-    AutoSwap_PRUint16    usWeightClass;
-    AutoSwap_PRUint16    usWidthClass;
-    AutoSwap_PRUint16    fsType;
-    AutoSwap_PRInt16     ySubscriptXSize;
-    AutoSwap_PRInt16     ySubscriptYSize;
-    AutoSwap_PRInt16     ySubscriptXOffset;
-    AutoSwap_PRInt16     ySubscriptYOffset;
-    AutoSwap_PRInt16     ySuperscriptXSize;
-    AutoSwap_PRInt16     ySuperscriptYSize;
-    AutoSwap_PRInt16     ySuperscriptXOffset;
-    AutoSwap_PRInt16     ySuperscriptYOffset;
-    AutoSwap_PRInt16     yStrikeoutSize;
-    AutoSwap_PRInt16     yStrikeoutPosition;
-    AutoSwap_PRInt16     sFamilyClass;
-    PRUint8              panose[10];
-    AutoSwap_PRUint32    unicodeRange1;
-    AutoSwap_PRUint32    unicodeRange2;
-    AutoSwap_PRUint32    unicodeRange3;
-    AutoSwap_PRUint32    unicodeRange4;
-    PRUint8              achVendID[4];
-    AutoSwap_PRUint16    fsSelection;
-    AutoSwap_PRUint16    usFirstCharIndex;
-    AutoSwap_PRUint16    usLastCharIndex;
-    AutoSwap_PRInt16     sTypoAscender;
-    AutoSwap_PRInt16     sTypoDescender;
-    AutoSwap_PRInt16     sTypoLineGap;
-    AutoSwap_PRUint16    usWinAscent;
-    AutoSwap_PRUint16    usWinDescent;
-    AutoSwap_PRUint32    codePageRange1;
-    AutoSwap_PRUint32    codePageRange2;
-    AutoSwap_PRInt16     sxHeight;
-    AutoSwap_PRInt16     sCapHeight;
-    AutoSwap_PRUint16    usDefaultChar;
-    AutoSwap_PRUint16    usBreakChar;
-    AutoSwap_PRUint16    usMaxContext;
-};
-
 // old 'kern' table, supported on Windows
 // see http://www.microsoft.com/typography/otspec/kern.htm
 struct KernTableVersion0 {
     AutoSwap_PRUint16    version; // 0x0000
     AutoSwap_PRUint16    nTables;
 };
 
 struct KernTableSubtableHeaderVersion0 {
diff --git a/gfx/thebes/src/gfxGDIFont.cpp b/gfx/thebes/src/gfxGDIFont.cpp
--- a/gfx/thebes/src/gfxGDIFont.cpp
+++ b/gfx/thebes/src/gfxGDIFont.cpp
@@ -227,17 +227,17 @@ gfxGDIFont::InitMetrics()
         mMetrics.underlineSize = (double)oMetrics.otmsUnderscoreSize;
         mMetrics.underlineOffset = (double)oMetrics.otmsUnderscorePosition;
 
         const MAT2 kIdentityMatrix = { {0, 1}, {0, 0}, {0, 0}, {0, 1} };
         GLYPHMETRICS gm;
         DWORD len = GetGlyphOutlineW(dc.GetDC(), PRUnichar('x'), GGO_METRICS, &gm, 0, nsnull, &kIdentityMatrix);
         if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) {
             // 56% of ascent, best guess for true type
-            mMetrics.xHeight = ROUND((double)metrics.tmAscent * 0.56);
+            mMetrics.xHeight = ROUND((double)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR);
         } else {
             mMetrics.xHeight = gm.gmptGlyphOrigin.y;
         }
         mMetrics.emHeight = metrics.tmHeight - metrics.tmInternalLeading;
         gfxFloat typEmHeight = (double)oMetrics.otmAscent - (double)oMetrics.otmDescent;
         mMetrics.emAscent = ROUND(mMetrics.emHeight * (double)oMetrics.otmAscent / typEmHeight);
         mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
     } else {
@@ -249,17 +249,17 @@ gfxGDIFont::InitMetrics()
         BOOL result = GetTextMetrics(dc.GetDC(), &metrics);
         if (!result) {
             NS_WARNING("Missing or corrupt font data, fasten your seatbelt");
             mIsValid = PR_FALSE;
             memset(&mMetrics, 0, sizeof(mMetrics));
             return;
         }
 
-        mMetrics.xHeight = ROUND((float)metrics.tmAscent * 0.56f); // 56% of ascent, best guess for non-true type
+        mMetrics.xHeight = ROUND((float)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR); // best guess for non-true type
         mMetrics.superscriptOffset = mMetrics.xHeight;
         mMetrics.subscriptOffset = mMetrics.xHeight;
         mMetrics.strikeoutSize = 1;
         mMetrics.strikeoutOffset = ROUND(mMetrics.xHeight / 2.0f); // 50% of xHeight
         mMetrics.underlineSize = 1;
         mMetrics.underlineOffset = -ROUND((float)metrics.tmDescent * 0.30f); // 30% of descent
         mMetrics.emHeight = metrics.tmHeight - metrics.tmInternalLeading;
         mMetrics.emAscent = metrics.tmAscent - metrics.tmInternalLeading;
diff --git a/gfx/thebes/src/gfxMacFont.cpp b/gfx/thebes/src/gfxMacFont.cpp
--- a/gfx/thebes/src/gfxMacFont.cpp
+++ b/gfx/thebes/src/gfxMacFont.cpp
@@ -80,17 +80,18 @@ gfxMacFont::gfxMacFont(MacOSFontEntry *a
 
     cairo_matrix_t sizeMatrix, ctm;
     cairo_matrix_init_identity(&ctm);
     cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
 
     // synthetic oblique by skewing via the font matrix
     PRBool needsOblique =
         (mFontEntry != NULL) &&
-        (!mFontEntry->IsItalic() && (mStyle.style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)));
+        (!mFontEntry->IsItalic() &&
+         (mStyle.style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)));
 
     if (needsOblique) {
         double skewfactor = (needsOblique ? Fix2X(kATSItalicQDSkew) : 0);
 
         cairo_matrix_t style;
         cairo_matrix_init(&style,
                           1,                //xx
                           0,                //yx
@@ -99,21 +100,23 @@ gfxMacFont::gfxMacFont(MacOSFontEntry *a
                           0,                //x0
                           0);               //y0
         cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
     }
 
     cairo_font_options_t *fontOptions = cairo_font_options_create();
 
     // turn off font anti-aliasing based on user pref setting
-    if (mAdjustedSize <= (float) gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
+    if (mAdjustedSize <=
+        (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
         cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE);
     }
 
-    mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, fontOptions);
+    mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm,
+                                           fontOptions);
     cairo_font_options_destroy(fontOptions);
 
     cairoerr = cairo_scaled_font_status(mScaledFont);
     if (cairoerr != CAIRO_STATUS_SUCCESS) {
         mIsValid = PR_FALSE;
 #ifdef DEBUG
         char warnBuf[1024];
         sprintf(warnBuf, "Failed to create scaled font: %s status: %d",
@@ -142,140 +145,115 @@ gfxMacFont::SetupCairoFont(gfxContext *a
         // Don't cairo_set_scaled_font as that would propagate the error to
         // the cairo_t, precluding any further drawing.
         return PR_FALSE;
     }
     cairo_set_scaled_font(aContext->GetCairo(), mScaledFont);
     return PR_TRUE;
 }
 
-static double
-RoundToNearestMultiple(double aValue, double aFraction)
-{
-    return floor(aValue/aFraction + 0.5) * aFraction;
-}
-
 void
 gfxMacFont::InitMetrics()
 {
-    gfxFloat size =
-        PR_MAX(((mAdjustedSize != 0.0f) ? mAdjustedSize : mStyle.size), 1.0f);
+    mIsValid = PR_FALSE;
+    ::memset(&mMetrics, 0, sizeof(mMetrics));
 
-    ATSFontMetrics atsMetrics;
-    OSStatus err;
-
-    err = ::ATSFontGetHorizontalMetrics(mATSFont, kATSOptionFlagsDefault,
-                                        &atsMetrics);
-    if (err != noErr) {
-        mIsValid = PR_FALSE;
-
-#ifdef DEBUG
-        char warnBuf[1024];
-        sprintf(warnBuf, "Bad font metrics for: %s err: %8.8x",
-                NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(), PRUint32(err));
-        NS_WARNING(warnBuf);
-#endif
+    mAdjustedSize = PR_MAX(mStyle.size, 1.0f);
+    if (!InitMetricsFromSfntTables(mMetrics, mAdjustedSize) &&
+        !InitMetricsFromATSMetrics()) {
         return;
     }
 
-    // create a temporary local CTFont for glyph measurement
-    CTFontRef aCTFont =
-        ::CTFontCreateWithPlatformFont(mATSFont, size, NULL, NULL);
+    CTFontRef ctFont = NULL;
+    if (mMetrics.xHeight == 0.0) {
+        // if we didn't get xHeight from metrics, try measuring sample glyphs
+        ctFont = ::CTFontCreateWithPlatformFont(mATSFont, mAdjustedSize,
+                                                NULL, NULL);
+        mMetrics.xHeight = ::CTFontGetXHeight(ctFont);
+        if (mMetrics.xHeight <= 0.0) {
+            mMetrics.xHeight = mMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR;
+        }
+    }
 
-    // prefer to get xHeight from ATS metrics (unhinted) rather than Core Text (hinted),
-    // see bug 429605.
-    if (atsMetrics.xHeight > 0)
-        mMetrics.xHeight = atsMetrics.xHeight * size;
-    else
-        mMetrics.xHeight = GetCharHeight(aCTFont, 'x');
-
-    if (mAdjustedSize == 0.0f) {
-        if (mMetrics.xHeight != 0.0f && mStyle.sizeAdjust != 0.0f) {
-            gfxFloat aspect = mMetrics.xHeight / size;
-            mAdjustedSize = mStyle.GetAdjustedSize(aspect);
-
-            // the recursive call to InitMetrics will see the adjusted size,
-            // and set up the rest of the metrics fields accordingly
-            InitMetrics();
-
-            // release our temporary CTFont
-            ::CFRelease(aCTFont);
+    if (mStyle.sizeAdjust != 0.0 && mStyle.size > 0.0 &&
+        mMetrics.xHeight > 0.0) {
+        // apply font-size-adjust, and recalculate metrics
+        gfxFloat aspect = mMetrics.xHeight / mStyle.size;
+        mAdjustedSize = mStyle.GetAdjustedSize(aspect);
+        mMetrics.xHeight = 0.0;
+        if (ctFont) {
+            ::CFRelease(ctFont);
+            ctFont = NULL;
+        }
+        if (!InitMetricsFromSfntTables(mMetrics, mAdjustedSize) &&
+            !InitMetricsFromATSMetrics()) {
             return;
         }
-        mAdjustedSize = size;
+        if (mMetrics.xHeight == 0.0) {
+            ctFont = ::CTFontCreateWithPlatformFont(mATSFont, mAdjustedSize,
+                                                    NULL, NULL);
+            mMetrics.xHeight = ::CTFontGetXHeight(ctFont);
+        }
     }
 
-    mMetrics.superscriptOffset = mMetrics.xHeight;
-    mMetrics.subscriptOffset = mMetrics.xHeight;
-    mMetrics.underlineOffset = atsMetrics.underlinePosition * size;
-    mMetrics.underlineSize = atsMetrics.underlineThickness * size;
-    mMetrics.strikeoutSize = mMetrics.underlineSize;
-    mMetrics.strikeoutOffset = mMetrics.xHeight / 2;
+    mMetrics.emHeight = mAdjustedSize;
 
-    mMetrics.externalLeading = atsMetrics.leading * size;
-    mMetrics.emHeight = size;
-    mMetrics.maxAscent =
-      NS_ceil(RoundToNearestMultiple(atsMetrics.ascent * size, 1/1024.0));
-    mMetrics.maxDescent =
-      NS_ceil(-RoundToNearestMultiple(atsMetrics.descent * size, 1/1024.0));
+    // Measure/calculate additional metrics, independent of whether we used
+    // the tables directly or ATS metrics APIs
 
-    mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent;
-    if (mMetrics.maxHeight - mMetrics.emHeight > 0.0)
-        mMetrics.internalLeading = mMetrics.maxHeight - mMetrics.emHeight;
-    else
-        mMetrics.internalLeading = 0.0;
-
-    mMetrics.maxAdvance = atsMetrics.maxAdvanceWidth * size + mSyntheticBoldOffset;
-
-    mMetrics.emAscent = mMetrics.maxAscent * mMetrics.emHeight / mMetrics.maxHeight;
-    mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
+    // create a CTFontRef if we didn't already do so above
+    if (ctFont == NULL) {
+        ctFont = ::CTFontCreateWithPlatformFont(mATSFont, mAdjustedSize,
+                                                NULL, NULL);
+    }
 
     PRUint32 glyphID;
-    float xWidth = GetCharWidth(aCTFont, 'x', &glyphID);
-    if (atsMetrics.avgAdvanceWidth != 0.0)
-        mMetrics.aveCharWidth = PR_MIN(atsMetrics.avgAdvanceWidth * size, xWidth);
-    else if (glyphID != 0)
-        mMetrics.aveCharWidth = xWidth;
-    else
-        mMetrics.aveCharWidth = mMetrics.maxAdvance;
-    mMetrics.aveCharWidth += mSyntheticBoldOffset;
-
-    if (mFontEntry->IsFixedPitch()) {
-        // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
-        // advance than the average character width... this forces
-        // those fonts to be recognized like fixed pitch fonts by layout.
-        mMetrics.maxAdvance = mMetrics.aveCharWidth;
+    if (mMetrics.aveCharWidth <= 0) {
+        mMetrics.aveCharWidth = GetCharWidth(ctFont, 'x', &glyphID);
+        if (glyphID == 0) {
+            // we didn't find 'x', so use maxAdvance rather than zero
+            mMetrics.aveCharWidth = mMetrics.maxAdvance;
+        }
     }
 
-    mMetrics.spaceWidth = GetCharWidth(aCTFont, ' ', &glyphID);
+    mMetrics.aveCharWidth += mSyntheticBoldOffset;
+    mMetrics.maxAdvance += mSyntheticBoldOffset;
+
+    mMetrics.spaceWidth = GetCharWidth(ctFont, ' ', &glyphID);
+    if (glyphID == 0) {
+        // no space glyph?!
+        mMetrics.spaceWidth = mMetrics.aveCharWidth;
+    }
     mSpaceGlyph = glyphID;
 
-    mMetrics.zeroOrAveCharWidth = GetCharWidth(aCTFont, '0', &glyphID);
-    if (glyphID == 0)
+    mMetrics.zeroOrAveCharWidth = GetCharWidth(ctFont, '0', &glyphID);
+    if (glyphID == 0) {
         mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
+    }
 
-    ::CFRelease(aCTFont);
+    ::CFRelease(ctFont);
+
+    CalculateDerivedMetrics(mMetrics);
 
     SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont);
 
     mIsValid = PR_TRUE;
 
 #if 0
     fprintf (stderr, "Font: %p (%s) size: %f\n", this,
              NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
-//    fprintf (stderr, "    fbounds.origin.x %f y %f size.width %f height %f\n", fbounds.origin.x, fbounds.origin.y, fbounds.size.width, fbounds.size.height);
     fprintf (stderr, "    emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
     fprintf (stderr, "    maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
     fprintf (stderr, "    internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading);
     fprintf (stderr, "    spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight);
     fprintf (stderr, "    uOff: %f uSize: %f stOff: %f stSize: %f suOff: %f suSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize, mMetrics.superscriptOffset, mMetrics.subscriptOffset);
 #endif
 }
 
-float
+gfxFloat
 gfxMacFont::GetCharWidth(CTFontRef aCTFont, PRUnichar aUniChar,
                          PRUint32 *aGlyphID)
 {
     UniChar c = aUniChar;
     CGGlyph glyph;
     if (::CTFontGetGlyphsForCharacters(aCTFont, &c, &glyph, 1)) {
         CGSize advance;
         ::CTFontGetAdvancesForGlyphs(aCTFont,
@@ -289,25 +267,45 @@ gfxMacFont::GetCharWidth(CTFontRef aCTFo
     }
 
     // couldn't get glyph for the char
     if (aGlyphID != nsnull)
         *aGlyphID = 0;
     return 0;
 }
 
-float
-gfxMacFont::GetCharHeight(CTFontRef aCTFont, PRUnichar aUniChar)
+// Try to initialize font metrics via ATS font metrics APIs;
+// return false on failure. We try to avoid this function
+// if possible (preferring InitMetricsFromSfntTables) because
+// ATSFontGetHorizontalMetrics() has been known to crash when
+// presented with bad fonts.
+PRBool
+gfxMacFont::InitMetricsFromATSMetrics()
 {
-    UniChar c = aUniChar;
-    CGGlyph glyph;
-    if (::CTFontGetGlyphsForCharacters(aCTFont, &c, &glyph, 1)) {
-        CGRect boundingRect;
-        ::CTFontGetBoundingRectsForGlyphs(aCTFont,
-                                          kCTFontHorizontalOrientation,
-                                          &glyph,
-                                          &boundingRect,
-                                          1);
-        return boundingRect.size.height;
+    ATSFontMetrics atsMetrics;
+    OSStatus err;
+
+    err = ::ATSFontGetHorizontalMetrics(mATSFont, kATSOptionFlagsDefault,
+                                        &atsMetrics);
+    if (err != noErr) {
+#ifdef DEBUG
+        char warnBuf[1024];
+        sprintf(warnBuf, "Bad font metrics for: %s err: %8.8x",
+                NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(), PRUint32(err));
+        NS_WARNING(warnBuf);
+#endif
+        return PR_FALSE;
     }
 
-    return 0;
+    mMetrics.underlineOffset = atsMetrics.underlinePosition * mAdjustedSize;
+    mMetrics.underlineSize = atsMetrics.underlineThickness * mAdjustedSize;
+
+    mMetrics.externalLeading = atsMetrics.leading * mAdjustedSize;
+
+    mMetrics.maxAscent = atsMetrics.ascent * mAdjustedSize;
+    mMetrics.maxDescent = -atsMetrics.descent * mAdjustedSize;
+
+    mMetrics.maxAdvance = atsMetrics.maxAdvanceWidth * mAdjustedSize;
+    mMetrics.aveCharWidth = atsMetrics.avgAdvanceWidth * mAdjustedSize;
+    mMetrics.xHeight = atsMetrics.xHeight * mAdjustedSize;
+
+    return PR_TRUE;
 }
diff --git a/gfx/thebes/src/gfxMacFont.h b/gfx/thebes/src/gfxMacFont.h
--- a/gfx/thebes/src/gfxMacFont.h
+++ b/gfx/thebes/src/gfxMacFont.h
@@ -53,39 +53,39 @@ public:
                PRBool aNeedsBold);
 
     virtual ~gfxMacFont();
 
     ATSFontRef GetATSFontRef() const { return mATSFont; }
 
     // TODO: probably should move this up to gfxFont
     // and ensure it is handled uniformly across all platforms
-    float GetAdjustedSize() const { return mAdjustedSize; }
+    gfxFloat GetAdjustedSize() const { return mAdjustedSize; }
 
     /* overrides for the pure virtual methods in gfxFont */
     virtual const gfxFont::Metrics& GetMetrics() {
         return mMetrics;
     }
 
     virtual PRUint32 GetSpaceGlyph() {
         return mSpaceGlyph;
     }
 
     virtual PRBool SetupCairoFont(gfxContext *aContext);
 
 protected:
     void InitMetrics();
+    PRBool InitMetricsFromATSMetrics();
 
-    float GetCharWidth(CTFontRef aCTFont, PRUnichar aUniChar,
-                       PRUint32 *aGlyphID);
-    float GetCharHeight(CTFontRef aCTFont, PRUnichar aUniChar);
+    gfxFloat GetCharWidth(CTFontRef aCTFont, PRUnichar aUniChar,
+                          PRUint32 *aGlyphID);
 
     ATSFontRef            mATSFont;
 
     cairo_font_face_t    *mFontFace;
     cairo_scaled_font_t  *mScaledFont;
 
     Metrics               mMetrics;
     PRUint32              mSpaceGlyph;
-    float                 mAdjustedSize;
+    gfxFloat              mAdjustedSize;
 };
 
 #endif /* GFX_MACFONT_H */
diff --git a/gfx/thebes/src/gfxMacPlatformFontList.h b/gfx/thebes/src/gfxMacPlatformFontList.h
--- a/gfx/thebes/src/gfxMacPlatformFontList.h
+++ b/gfx/thebes/src/gfxMacPlatformFontList.h
@@ -61,24 +61,24 @@ public:
     friend class gfxMacPlatformFontList;
 
     MacOSFontEntry(const nsAString& aPostscriptName, PRInt32 aWeight,
                    gfxFontFamily *aFamily, PRBool aIsStandardFace = PR_FALSE);
 
     ATSFontRef GetFontRef();
     nsresult ReadCMAP();
 
+    virtual nsresult GetFontTable(PRUint32 aTableTag, nsTArray<PRUint8>& aBuffer);
+
 protected:
     // for use with data fonts
     MacOSFontEntry(const nsAString& aPostscriptName, ATSFontRef aFontRef,
                    PRUint16 aWeight, PRUint16 aStretch, PRUint32 aItalicStyle,
                    gfxUserFontData *aUserFontData);
 
-    virtual nsresult GetFontTable(PRUint32 aTableTag, nsTArray<PRUint8>& aBuffer);
-
     virtual gfxFont* CreateFontInstance(const gfxFontStyle *aFontStyle, PRBool aNeedsBold);
 
     ATSFontRef mATSFontRef;
     PRPackedBool mATSFontRefInitialized;
 };
 
 class gfxMacPlatformFontList : public gfxPlatformFontList {
 public:
diff --git a/gfx/thebes/src/gfxMacPlatformFontList.mm b/gfx/thebes/src/gfxMacPlatformFontList.mm
--- a/gfx/thebes/src/gfxMacPlatformFontList.mm
+++ b/gfx/thebes/src/gfxMacPlatformFontList.mm
@@ -287,25 +287,29 @@ MacOSFontEntry::ReadCMAP()
 }
 
 nsresult
 MacOSFontEntry::GetFontTable(PRUint32 aTableTag, nsTArray<PRUint8>& aBuffer)
 {
     nsAutoreleasePool localPool;
 
     ATSFontRef fontRef = GetFontRef();
-    if (fontRef == (ATSFontRef)kATSUInvalidFontID)
+    if (fontRef == (ATSFontRef)kATSUInvalidFontID) {
         return NS_ERROR_FAILURE;
+    }
 
     ByteCount dataLength;
     OSStatus status = ::ATSFontGetTable(fontRef, aTableTag, 0, 0, 0, &dataLength);
-    NS_ENSURE_TRUE(status == noErr, NS_ERROR_FAILURE);
+    if (status != noErr) {
+        return NS_ERROR_FAILURE;
+    }
 
-    if (!aBuffer.AppendElements(dataLength))
+    if (!aBuffer.AppendElements(dataLength)) {
         return NS_ERROR_OUT_OF_MEMORY;
+    }
     PRUint8 *dataPtr = aBuffer.Elements();
 
     status = ::ATSFontGetTable(fontRef, aTableTag, 0, dataLength, dataPtr, &dataLength);
     NS_ENSURE_TRUE(status == noErr, NS_ERROR_FAILURE);
 
     return NS_OK;
 }
 
