diff --git a/gfx/thebes/gfxFT2FontBase.cpp b/gfx/thebes/gfxFT2FontBase.cpp index f0408ee245a6..0b411b960aea 100644 --- a/gfx/thebes/gfxFT2FontBase.cpp +++ b/gfx/thebes/gfxFT2FontBase.cpp @@ -9,14 +9,6 @@ #include "mozilla/Likely.h" #include "gfxFontConstants.h" #include "gfxFontUtils.h" -#include - -#include FT_TRUETYPE_TAGS_H -#include FT_TRUETYPE_TABLES_H - -#ifndef FT_FACE_FLAG_COLOR -#define FT_FACE_FLAG_COLOR ( 1L << 14 ) -#endif using namespace mozilla::gfx; @@ -24,12 +16,13 @@ gfxFT2FontBase::gfxFT2FontBase(const RefPtr& aUnscaledFont cairo_scaled_font_t *aScaledFont, gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle) - : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault, aScaledFont) - , mSpaceGlyph(0) + : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault, aScaledFont), + mSpaceGlyph(0), + mHasMetrics(false) { cairo_scaled_font_reference(mScaledFont); - - InitMetrics(); + gfxFT2LockedFace face(this); + mFUnitsConvFactor = face.XScale(); } gfxFT2FontBase::~gfxFT2FontBase() @@ -116,315 +109,21 @@ gfxFT2FontBase::GetGlyphExtents(uint32_t aGlyph, cairo_text_extents_t* aExtents) cairo_scaled_font_glyph_extents(CairoScaledFont(), glyphs, 1, aExtents); } -// aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics -static inline FT_Long -ScaleRoundDesignUnits(FT_Short aDesignMetric, FT_Fixed aScale) +const gfxFont::Metrics& +gfxFT2FontBase::GetHorizontalMetrics() { - FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale); - return ROUND_26_6_TO_INT(fixed26dot6); -} - -// Snap a line to pixels while keeping the center and size of the line as -// close to the original position as possible. -// -// Pango does similar snapping for underline and strikethrough when fonts are -// hinted, but nsCSSRendering::GetTextDecorationRectInternal always snaps the -// top and size of lines. Optimizing the distance between the line and -// baseline is probably good for the gap between text and underline, but -// optimizing the center of the line is better for positioning strikethough. -static void -SnapLineToPixels(gfxFloat& aOffset, gfxFloat& aSize) -{ - gfxFloat snappedSize = std::max(floor(aSize + 0.5), 1.0); - // Correct offset for change in size - gfxFloat offset = aOffset - 0.5 * (aSize - snappedSize); - // Snap offset - aOffset = floor(offset + 0.5); - aSize = snappedSize; -} - -/** - * Get extents for a simple character representable by a single glyph. - * The return value is the glyph id of that glyph or zero if no such glyph - * exists. aExtents is only set when this returns a non-zero glyph id. - */ -uint32_t -gfxFT2FontBase::GetCharExtents(char aChar, cairo_text_extents_t* aExtents) -{ - FT_UInt gid = GetGlyph(aChar); - if (gid) { - GetGlyphExtents(gid, aExtents); - } - return gid; -} - -void -gfxFT2FontBase::InitMetrics() -{ - mFUnitsConvFactor = 0.0; + if (mHasMetrics) + return mMetrics; if (MOZ_UNLIKELY(GetStyle()->size <= 0.0) || MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0)) { - memset(&mMetrics, 0, sizeof(mMetrics)); // zero initialize + new(&mMetrics) gfxFont::Metrics(); // zero initialize mSpaceGlyph = GetGlyph(' '); - return; - } - - FT_Face face; - FT_Size_Metrics ftMetrics; - { - // Size metrics need to be copied within the scope of the lock to ensure - // it doesn't change out from under us. - gfxFT2LockedFace lockedFace(this); - face = lockedFace.get(); - if (face) { - ftMetrics = face->size->metrics; - } - // Release the face lock to safely load glyphs with GetCharExtents if - // necessary without recursively locking. The face will stick around - // even though this lock is released, and the fields/tables used on it - // other than size metrics are constant. - } - - if (MOZ_UNLIKELY(!face)) { - // No face. This unfortunate situation might happen if the font - // file is (re)moved at the wrong time. - const gfxFloat emHeight = GetAdjustedSize(); - mMetrics.emHeight = emHeight; - mMetrics.maxAscent = mMetrics.emAscent = 0.8 * emHeight; - mMetrics.maxDescent = mMetrics.emDescent = 0.2 * emHeight; - mMetrics.maxHeight = emHeight; - mMetrics.internalLeading = 0.0; - mMetrics.externalLeading = 0.2 * emHeight; - const gfxFloat spaceWidth = 0.5 * emHeight; - mMetrics.spaceWidth = spaceWidth; - mMetrics.maxAdvance = spaceWidth; - mMetrics.aveCharWidth = spaceWidth; - mMetrics.zeroOrAveCharWidth = spaceWidth; - const gfxFloat xHeight = 0.5 * emHeight; - mMetrics.xHeight = xHeight; - mMetrics.capHeight = mMetrics.maxAscent; - const gfxFloat underlineSize = emHeight / 14.0; - mMetrics.underlineSize = underlineSize; - mMetrics.underlineOffset = -underlineSize; - mMetrics.strikeoutOffset = 0.25 * emHeight; - mMetrics.strikeoutSize = underlineSize; - - SanitizeMetrics(&mMetrics, false); - return; - } - - mMetrics.maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender); - mMetrics.maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender); - mMetrics.maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance); - gfxFloat lineHeight = FLOAT_FROM_26_6(ftMetrics.height); - - gfxFloat emHeight; - // Scale for vertical design metric conversion: pixels per design unit. - // If this remains at 0.0, we can't use metrics from OS/2 etc. - gfxFloat yScale = 0.0; - if (FT_IS_SCALABLE(face)) { - // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not - // have subpixel accuracy. - // - // FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its - // (fractional) value is a factor that converts vertical metrics from - // design units to units of 1/64 pixels, so that the result may be - // interpreted as pixels in 26.6 fixed point format. - mFUnitsConvFactor = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.x_scale)); - yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale)); - emHeight = face->units_per_EM * yScale; - } else { // Not scalable. - emHeight = ftMetrics.y_ppem; - // FT_Face doc says units_per_EM and a bunch of following fields - // are "only relevant to scalable outlines". If it's an sfnt, - // we can get units_per_EM from the 'head' table instead; otherwise, - // we don't have a unitsPerEm value so we can't compute/use yScale or - // mFUnitsConvFactor (x scale). - const TT_Header* head = - static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_head)); - if (head) { - // Bug 1267909 - Even if the font is not explicitly scalable, - // if the face has color bitmaps, it should be treated as scalable - // and scaled to the desired size. Metrics based on y_ppem need - // to be rescaled for the adjusted size. This makes metrics agree - // with the scales we pass to Cairo for Fontconfig fonts. - if (face->face_flags & FT_FACE_FLAG_COLOR) { - emHeight = GetAdjustedSize(); - gfxFloat adjustScale = emHeight / ftMetrics.y_ppem; - mMetrics.maxAscent *= adjustScale; - mMetrics.maxDescent *= adjustScale; - mMetrics.maxAdvance *= adjustScale; - lineHeight *= adjustScale; - } - gfxFloat emUnit = head->Units_Per_EM; - mFUnitsConvFactor = ftMetrics.x_ppem / emUnit; - yScale = emHeight / emUnit; - } - } - - TT_OS2 *os2 = - static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_os2)); - - if (os2 && os2->sTypoAscender && yScale > 0.0) { - mMetrics.emAscent = os2->sTypoAscender * yScale; - mMetrics.emDescent = -os2->sTypoDescender * yScale; - FT_Short typoHeight = - os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap; - lineHeight = typoHeight * yScale; - - // If the OS/2 fsSelection USE_TYPO_METRICS bit is set, - // set maxAscent/Descent from the sTypo* fields instead of hhea. - const uint16_t kUseTypoMetricsMask = 1 << 7; - if (os2->fsSelection & kUseTypoMetricsMask) { - mMetrics.maxAscent = NS_round(mMetrics.emAscent); - mMetrics.maxDescent = NS_round(mMetrics.emDescent); - } else { - // maxAscent/maxDescent get used for frame heights, and some fonts - // don't have the HHEA table ascent/descent set (bug 279032). - // We use NS_round here to parallel the pixel-rounded values that - // freetype gives us for ftMetrics.ascender/descender. - mMetrics.maxAscent = - std::max(mMetrics.maxAscent, NS_round(mMetrics.emAscent)); - mMetrics.maxDescent = - std::max(mMetrics.maxDescent, NS_round(mMetrics.emDescent)); - } } else { - mMetrics.emAscent = mMetrics.maxAscent; - mMetrics.emDescent = mMetrics.maxDescent; + gfxFT2LockedFace face(this); + face.GetMetrics(&mMetrics, &mSpaceGlyph); } - cairo_text_extents_t extents; - mSpaceGlyph = GetCharExtents(' ', &extents); - if (mSpaceGlyph) { - mMetrics.spaceWidth = extents.x_advance; - } else { - mMetrics.spaceWidth = mMetrics.maxAdvance; // guess - } - - mMetrics.zeroOrAveCharWidth = 0.0; - if (GetCharExtents('0', &extents)) { - mMetrics.zeroOrAveCharWidth = extents.x_advance; - } - - // Prefering a measured x over sxHeight because sxHeight doesn't consider - // hinting, but maybe the x extents are not quite right in some fancy - // script fonts. CSS 2.1 suggests possibly using the height of an "o", - // which would have a more consistent glyph across fonts. - if (GetCharExtents('x', &extents) && extents.y_bearing < 0.0) { - mMetrics.xHeight = -extents.y_bearing; - mMetrics.aveCharWidth = extents.x_advance; - } else { - if (os2 && os2->sxHeight && yScale > 0.0) { - mMetrics.xHeight = os2->sxHeight * yScale; - } else { - // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is - // impossible or impractical to determine the x-height, a value of - // 0.5em should be used." - mMetrics.xHeight = 0.5 * emHeight; - } - mMetrics.aveCharWidth = 0.0; // updated below - } - - if (GetCharExtents('H', &extents) && extents.y_bearing < 0.0) { - mMetrics.capHeight = -extents.y_bearing; - } else { - if (os2 && os2->sCapHeight && yScale > 0.0) { - mMetrics.capHeight = os2->sCapHeight * yScale; - } else { - mMetrics.capHeight = mMetrics.maxAscent; - } - } - - // aveCharWidth is used for the width of text input elements so be - // liberal rather than conservative in the estimate. - if (os2 && os2->xAvgCharWidth) { - // Round to pixels as this is compared with maxAdvance to guess - // whether this is a fixed width font. - gfxFloat avgCharWidth = - ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale); - mMetrics.aveCharWidth = - std::max(mMetrics.aveCharWidth, avgCharWidth); - } - mMetrics.aveCharWidth = - std::max(mMetrics.aveCharWidth, mMetrics.zeroOrAveCharWidth); - if (mMetrics.aveCharWidth == 0.0) { - mMetrics.aveCharWidth = mMetrics.spaceWidth; - } - if (mMetrics.zeroOrAveCharWidth == 0.0) { - mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth; - } - // Apparently hinting can mean that max_advance is not always accurate. - mMetrics.maxAdvance = - std::max(mMetrics.maxAdvance, mMetrics.aveCharWidth); - - // gfxFont::Metrics::underlineOffset is the position of the top of the - // underline. - // - // FT_FaceRec documentation describes underline_position as "the - // center of the underlining stem". This was the original definition - // of the PostScript metric, but in the PostScript table of OpenType - // fonts the metric is "the top of the underline" - // (http://www.microsoft.com/typography/otspec/post.htm), and FreeType - // (up to version 2.3.7) doesn't make any adjustment. - // - // Therefore get the underline position directly from the table - // ourselves when this table exists. Use FreeType's metrics for - // other (including older PostScript) fonts. - if (face->underline_position && face->underline_thickness && yScale > 0.0) { - mMetrics.underlineSize = face->underline_thickness * yScale; - TT_Postscript *post = static_cast - (FT_Get_Sfnt_Table(face, ft_sfnt_post)); - if (post && post->underlinePosition) { - mMetrics.underlineOffset = post->underlinePosition * yScale; - } else { - mMetrics.underlineOffset = face->underline_position * yScale - + 0.5 * mMetrics.underlineSize; - } - } else { // No underline info. - // Imitate Pango. - mMetrics.underlineSize = emHeight / 14.0; - mMetrics.underlineOffset = -mMetrics.underlineSize; - } - - if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) { - mMetrics.strikeoutSize = os2->yStrikeoutSize * yScale; - mMetrics.strikeoutOffset = os2->yStrikeoutPosition * yScale; - } else { // No strikeout info. - mMetrics.strikeoutSize = mMetrics.underlineSize; - // Use OpenType spec's suggested position for Roman font. - mMetrics.strikeoutOffset = emHeight * 409.0 / 2048.0 - + 0.5 * mMetrics.strikeoutSize; - } - SnapLineToPixels(mMetrics.strikeoutOffset, mMetrics.strikeoutSize); - - mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent; - - // Make the line height an integer number of pixels so that lines will be - // equally spaced (rather than just being snapped to pixels, some up and - // some down). Layout calculates line height from the emHeight + - // internalLeading + externalLeading, but first each of these is rounded - // to layout units. To ensure that the result is an integer number of - // pixels, round each of the components to pixels. - mMetrics.emHeight = floor(emHeight + 0.5); - - // maxHeight will normally be an integer, but round anyway in case - // FreeType is configured differently. - mMetrics.internalLeading = - floor(mMetrics.maxHeight - mMetrics.emHeight + 0.5); - - // Text input boxes currently don't work well with lineHeight - // significantly less than maxHeight (with Verdana, for example). - lineHeight = floor(std::max(lineHeight, mMetrics.maxHeight) + 0.5); - mMetrics.externalLeading = - lineHeight - mMetrics.internalLeading - mMetrics.emHeight; - - // Ensure emAscent + emDescent == emHeight - gfxFloat sum = mMetrics.emAscent + mMetrics.emDescent; - mMetrics.emAscent = sum > 0.0 ? - mMetrics.emAscent * mMetrics.emHeight / sum : 0.0; - mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent; - SanitizeMetrics(&mMetrics, false); #if 0 @@ -438,11 +137,8 @@ gfxFT2FontBase::InitMetrics() 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\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize); #endif -} -const gfxFont::Metrics& -gfxFT2FontBase::GetHorizontalMetrics() -{ + mHasMetrics = true; return mMetrics; } @@ -450,6 +146,7 @@ gfxFT2FontBase::GetHorizontalMetrics() uint32_t gfxFT2FontBase::GetSpaceGlyph() { + GetHorizontalMetrics(); return mSpaceGlyph; } diff --git a/gfx/thebes/gfxFT2FontBase.h b/gfx/thebes/gfxFT2FontBase.h index 5c2eab5bfc29..c4f89221eb2f 100644 --- a/gfx/thebes/gfxFT2FontBase.h +++ b/gfx/thebes/gfxFT2FontBase.h @@ -36,14 +36,11 @@ public: virtual FontType GetType() const override { return FONT_TYPE_FT2; } -private: - uint32_t GetCharExtents(char aChar, cairo_text_extents_t* aExtents); - void InitMetrics(); - protected: virtual const Metrics& GetHorizontalMetrics() override; uint32_t mSpaceGlyph; + bool mHasMetrics; Metrics mMetrics; }; diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp index fb406e66cbaa..62a9f4672052 100644 --- a/gfx/thebes/gfxFT2Fonts.cpp +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -90,7 +90,7 @@ gfxFT2Font::AddRange(const char16_t *aText, uint32_t aOffset, cgd = cgdNext; cgdNext = nullptr; } else { - cgd = GetGlyphDataForChar(face, ch); + cgd = GetGlyphDataForChar(ch); } FT_UInt gid = cgd->glyphIndex; @@ -108,7 +108,7 @@ gfxFT2Font::AddRange(const char16_t *aText, uint32_t aOffset, if (FT_HAS_KERNING(face) && i + 1 < aLength) { chNext = aText[i + 1]; if (chNext != 0) { - cgdNext = GetGlyphDataForChar(face, chNext); + cgdNext = GetGlyphDataForChar(chNext); gidNext = cgdNext->glyphIndex; if (gidNext && gidNext != spaceGlyph) lsbDeltaNext = cgdNext->lsbDelta; @@ -174,8 +174,11 @@ gfxFT2Font::~gfxFT2Font() } void -gfxFT2Font::FillGlyphDataForChar(FT_Face face, uint32_t ch, CachedGlyphData *gd) +gfxFT2Font::FillGlyphDataForChar(uint32_t ch, CachedGlyphData *gd) { + gfxFT2LockedFace faceLock(this); + FT_Face face = faceLock.get(); + if (!face->charmap || face->charmap->encoding != FT_ENCODING_UNICODE) { FT_Select_Charmap(face, FT_ENCODING_UNICODE); } diff --git a/gfx/thebes/gfxFT2Fonts.h b/gfx/thebes/gfxFT2Fonts.h index 949c6c33e229..ff9ebd8992fb 100644 --- a/gfx/thebes/gfxFT2Fonts.h +++ b/gfx/thebes/gfxFT2Fonts.h @@ -28,12 +28,6 @@ public: // new functions FT2FontEntry *GetFontEntry(); - virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, - FontCacheSizes* aSizes) const override; - virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, - FontCacheSizes* aSizes) const override; - -protected: struct CachedGlyphData { CachedGlyphData() : glyphIndex(0xffffffffU) { } @@ -47,7 +41,7 @@ protected: int32_t xAdvance; }; - const CachedGlyphData* GetGlyphDataForChar(FT_face aFace, uint32_t ch) { + const CachedGlyphData* GetGlyphDataForChar(uint32_t ch) { CharGlyphMapEntryType *entry = mCharGlyphCache.PutEntry(ch); if (!entry) @@ -55,12 +49,18 @@ protected: if (entry->mData.glyphIndex == 0xffffffffU) { // this is a new entry, fill it - FillGlyphDataForChar(aFace, ch, &entry->mData); + FillGlyphDataForChar(ch, &entry->mData); } return &entry->mData; } + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + +protected: bool ShapeText(DrawTarget *aDrawTarget, const char16_t *aText, uint32_t aOffset, @@ -70,7 +70,7 @@ protected: RoundingFlags aRounding, gfxShapedText *aShapedText) override; - void FillGlyphDataForChar(FT_Face face, uint32_t ch, CachedGlyphData *gd); + void FillGlyphDataForChar(uint32_t ch, CachedGlyphData *gd); void AddRange(const char16_t *aText, uint32_t aOffset, diff --git a/gfx/thebes/gfxFT2Utils.cpp b/gfx/thebes/gfxFT2Utils.cpp index 1f28feb1fd9c..38d2ae75603e 100644 --- a/gfx/thebes/gfxFT2Utils.cpp +++ b/gfx/thebes/gfxFT2Utils.cpp @@ -6,6 +6,13 @@ #include "gfxFT2FontBase.h" #include "gfxFT2Utils.h" #include "mozilla/Likely.h" +#include FT_TRUETYPE_TAGS_H +#include FT_TRUETYPE_TABLES_H +#include + +#ifndef FT_FACE_FLAG_COLOR +#define FT_FACE_FLAG_COLOR ( 1L << 14 ) +#endif #ifdef HAVE_FONTCONFIG_FCFREETYPE_H #include @@ -13,6 +20,279 @@ #include "prlink.h" +// aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics +static inline FT_Long +ScaleRoundDesignUnits(FT_Short aDesignMetric, FT_Fixed aScale) +{ + FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale); + return ROUND_26_6_TO_INT(fixed26dot6); +} + +// Snap a line to pixels while keeping the center and size of the line as +// close to the original position as possible. +// +// Pango does similar snapping for underline and strikethrough when fonts are +// hinted, but nsCSSRendering::GetTextDecorationRectInternal always snaps the +// top and size of lines. Optimizing the distance between the line and +// baseline is probably good for the gap between text and underline, but +// optimizing the center of the line is better for positioning strikethough. +static void +SnapLineToPixels(gfxFloat& aOffset, gfxFloat& aSize) +{ + gfxFloat snappedSize = std::max(floor(aSize + 0.5), 1.0); + // Correct offset for change in size + gfxFloat offset = aOffset - 0.5 * (aSize - snappedSize); + // Snap offset + aOffset = floor(offset + 0.5); + aSize = snappedSize; +} + +void +gfxFT2LockedFace::GetMetrics(gfxFont::Metrics* aMetrics, + uint32_t* aSpaceGlyph) +{ + NS_PRECONDITION(aMetrics != nullptr, "aMetrics must not be NULL"); + NS_PRECONDITION(aSpaceGlyph != nullptr, "aSpaceGlyph must not be NULL"); + + if (MOZ_UNLIKELY(!mFace)) { + // No face. This unfortunate situation might happen if the font + // file is (re)moved at the wrong time. + const gfxFloat emHeight = mGfxFont->GetAdjustedSize(); + aMetrics->emHeight = emHeight; + aMetrics->maxAscent = aMetrics->emAscent = 0.8 * emHeight; + aMetrics->maxDescent = aMetrics->emDescent = 0.2 * emHeight; + aMetrics->maxHeight = emHeight; + aMetrics->internalLeading = 0.0; + aMetrics->externalLeading = 0.2 * emHeight; + const gfxFloat spaceWidth = 0.5 * emHeight; + aMetrics->spaceWidth = spaceWidth; + aMetrics->maxAdvance = spaceWidth; + aMetrics->aveCharWidth = spaceWidth; + aMetrics->zeroOrAveCharWidth = spaceWidth; + const gfxFloat xHeight = 0.5 * emHeight; + aMetrics->xHeight = xHeight; + aMetrics->capHeight = aMetrics->maxAscent; + const gfxFloat underlineSize = emHeight / 14.0; + aMetrics->underlineSize = underlineSize; + aMetrics->underlineOffset = -underlineSize; + aMetrics->strikeoutOffset = 0.25 * emHeight; + aMetrics->strikeoutSize = underlineSize; + + *aSpaceGlyph = 0; + return; + } + + const FT_Size_Metrics& ftMetrics = mFace->size->metrics; + + aMetrics->maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender); + aMetrics->maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender); + aMetrics->maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance); + gfxFloat lineHeight = FLOAT_FROM_26_6(ftMetrics.height); + + gfxFloat emHeight; + // Scale for vertical design metric conversion: pixels per design unit. + // If this remains at 0.0, we can't use metrics from OS/2 etc. + gfxFloat yScale = 0.0; + if (FT_IS_SCALABLE(mFace)) { + // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not + // have subpixel accuracy. + // + // FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its + // (fractional) value is a factor that converts vertical metrics from + // design units to units of 1/64 pixels, so that the result may be + // interpreted as pixels in 26.6 fixed point format. + yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale)); + emHeight = mFace->units_per_EM * yScale; + } else { // Not scalable. + emHeight = ftMetrics.y_ppem; + // FT_Face doc says units_per_EM and a bunch of following fields + // are "only relevant to scalable outlines". If it's an sfnt, + // we can get units_per_EM from the 'head' table instead; otherwise, + // we don't have a unitsPerEm value so we can't compute/use yScale. + const TT_Header* head = + static_cast(FT_Get_Sfnt_Table(mFace, ft_sfnt_head)); + if (head) { + // Bug 1267909 - Even if the font is not explicitly scalable, + // if the face has color bitmaps, it should be treated as scalable + // and scaled to the desired size. Metrics based on y_ppem need + // to be rescaled for the adjusted size. This makes metrics agree + // with the scales we pass to Cairo for Fontconfig fonts. + if (mFace->face_flags & FT_FACE_FLAG_COLOR) { + emHeight = mGfxFont->GetAdjustedSize(); + gfxFloat adjustScale = emHeight / ftMetrics.y_ppem; + aMetrics->maxAscent *= adjustScale; + aMetrics->maxDescent *= adjustScale; + aMetrics->maxAdvance *= adjustScale; + lineHeight *= adjustScale; + } + gfxFloat emUnit = head->Units_Per_EM; + yScale = emHeight / emUnit; + } + } + + TT_OS2 *os2 = + static_cast(FT_Get_Sfnt_Table(mFace, ft_sfnt_os2)); + + if (os2 && os2->sTypoAscender && yScale > 0.0) { + aMetrics->emAscent = os2->sTypoAscender * yScale; + aMetrics->emDescent = -os2->sTypoDescender * yScale; + FT_Short typoHeight = + os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap; + lineHeight = typoHeight * yScale; + + // If the OS/2 fsSelection USE_TYPO_METRICS bit is set, + // set maxAscent/Descent from the sTypo* fields instead of hhea. + const uint16_t kUseTypoMetricsMask = 1 << 7; + if (os2->fsSelection & kUseTypoMetricsMask) { + aMetrics->maxAscent = NS_round(aMetrics->emAscent); + aMetrics->maxDescent = NS_round(aMetrics->emDescent); + } else { + // maxAscent/maxDescent get used for frame heights, and some fonts + // don't have the HHEA table ascent/descent set (bug 279032). + // We use NS_round here to parallel the pixel-rounded values that + // freetype gives us for ftMetrics.ascender/descender. + aMetrics->maxAscent = + std::max(aMetrics->maxAscent, NS_round(aMetrics->emAscent)); + aMetrics->maxDescent = + std::max(aMetrics->maxDescent, NS_round(aMetrics->emDescent)); + } + } else { + aMetrics->emAscent = aMetrics->maxAscent; + aMetrics->emDescent = aMetrics->maxDescent; + } + + cairo_text_extents_t extents; + *aSpaceGlyph = GetCharExtents(' ', &extents); + if (*aSpaceGlyph) { + aMetrics->spaceWidth = extents.x_advance; + } else { + aMetrics->spaceWidth = aMetrics->maxAdvance; // guess + } + + aMetrics->zeroOrAveCharWidth = 0.0; + if (GetCharExtents('0', &extents)) { + aMetrics->zeroOrAveCharWidth = extents.x_advance; + } + + // Prefering a measured x over sxHeight because sxHeight doesn't consider + // hinting, but maybe the x extents are not quite right in some fancy + // script fonts. CSS 2.1 suggests possibly using the height of an "o", + // which would have a more consistent glyph across fonts. + if (GetCharExtents('x', &extents) && extents.y_bearing < 0.0) { + aMetrics->xHeight = -extents.y_bearing; + aMetrics->aveCharWidth = extents.x_advance; + } else { + if (os2 && os2->sxHeight && yScale > 0.0) { + aMetrics->xHeight = os2->sxHeight * yScale; + } else { + // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is + // impossible or impractical to determine the x-height, a value of + // 0.5em should be used." + aMetrics->xHeight = 0.5 * emHeight; + } + aMetrics->aveCharWidth = 0.0; // updated below + } + + if (GetCharExtents('H', &extents) && extents.y_bearing < 0.0) { + aMetrics->capHeight = -extents.y_bearing; + } else { + if (os2 && os2->sCapHeight && yScale > 0.0) { + aMetrics->capHeight = os2->sCapHeight * yScale; + } else { + aMetrics->capHeight = aMetrics->maxAscent; + } + } + + // aveCharWidth is used for the width of text input elements so be + // liberal rather than conservative in the estimate. + if (os2 && os2->xAvgCharWidth) { + // Round to pixels as this is compared with maxAdvance to guess + // whether this is a fixed width font. + gfxFloat avgCharWidth = + ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale); + aMetrics->aveCharWidth = + std::max(aMetrics->aveCharWidth, avgCharWidth); + } + aMetrics->aveCharWidth = + std::max(aMetrics->aveCharWidth, aMetrics->zeroOrAveCharWidth); + if (aMetrics->aveCharWidth == 0.0) { + aMetrics->aveCharWidth = aMetrics->spaceWidth; + } + if (aMetrics->zeroOrAveCharWidth == 0.0) { + aMetrics->zeroOrAveCharWidth = aMetrics->aveCharWidth; + } + // Apparently hinting can mean that max_advance is not always accurate. + aMetrics->maxAdvance = + std::max(aMetrics->maxAdvance, aMetrics->aveCharWidth); + + // gfxFont::Metrics::underlineOffset is the position of the top of the + // underline. + // + // FT_FaceRec documentation describes underline_position as "the + // center of the underlining stem". This was the original definition + // of the PostScript metric, but in the PostScript table of OpenType + // fonts the metric is "the top of the underline" + // (http://www.microsoft.com/typography/otspec/post.htm), and FreeType + // (up to version 2.3.7) doesn't make any adjustment. + // + // Therefore get the underline position directly from the table + // ourselves when this table exists. Use FreeType's metrics for + // other (including older PostScript) fonts. + if (mFace->underline_position && mFace->underline_thickness && yScale > 0.0) { + aMetrics->underlineSize = mFace->underline_thickness * yScale; + TT_Postscript *post = static_cast + (FT_Get_Sfnt_Table(mFace, ft_sfnt_post)); + if (post && post->underlinePosition) { + aMetrics->underlineOffset = post->underlinePosition * yScale; + } else { + aMetrics->underlineOffset = mFace->underline_position * yScale + + 0.5 * aMetrics->underlineSize; + } + } else { // No underline info. + // Imitate Pango. + aMetrics->underlineSize = emHeight / 14.0; + aMetrics->underlineOffset = -aMetrics->underlineSize; + } + + if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) { + aMetrics->strikeoutSize = os2->yStrikeoutSize * yScale; + aMetrics->strikeoutOffset = os2->yStrikeoutPosition * yScale; + } else { // No strikeout info. + aMetrics->strikeoutSize = aMetrics->underlineSize; + // Use OpenType spec's suggested position for Roman font. + aMetrics->strikeoutOffset = emHeight * 409.0 / 2048.0 + + 0.5 * aMetrics->strikeoutSize; + } + SnapLineToPixels(aMetrics->strikeoutOffset, aMetrics->strikeoutSize); + + aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent; + + // Make the line height an integer number of pixels so that lines will be + // equally spaced (rather than just being snapped to pixels, some up and + // some down). Layout calculates line height from the emHeight + + // internalLeading + externalLeading, but first each of these is rounded + // to layout units. To ensure that the result is an integer number of + // pixels, round each of the components to pixels. + aMetrics->emHeight = floor(emHeight + 0.5); + + // maxHeight will normally be an integer, but round anyway in case + // FreeType is configured differently. + aMetrics->internalLeading = + floor(aMetrics->maxHeight - aMetrics->emHeight + 0.5); + + // Text input boxes currently don't work well with lineHeight + // significantly less than maxHeight (with Verdana, for example). + lineHeight = floor(std::max(lineHeight, aMetrics->maxHeight) + 0.5); + aMetrics->externalLeading = + lineHeight - aMetrics->internalLeading - aMetrics->emHeight; + + // Ensure emAscent + emDescent == emHeight + gfxFloat sum = aMetrics->emAscent + aMetrics->emDescent; + aMetrics->emAscent = sum > 0.0 ? + aMetrics->emAscent * aMetrics->emHeight / sum : 0.0; + aMetrics->emDescent = aMetrics->emHeight - aMetrics->emAscent; +} + uint32_t gfxFT2LockedFace::GetGlyph(uint32_t aCharCode) { @@ -65,6 +345,22 @@ gfxFT2LockedFace::GetUVSGlyph(uint32_t aCharCode, uint32_t aVariantSelector) return (*sGetCharVariantPtr)(mFace, aCharCode, aVariantSelector); } +uint32_t +gfxFT2LockedFace::GetCharExtents(char aChar, cairo_text_extents_t* aExtents) +{ + NS_PRECONDITION(aExtents != nullptr, "aExtents must not be NULL"); + + if (!mFace) + return 0; + + FT_UInt gid = mGfxFont->GetGlyph(aChar); + if (gid) { + mGfxFont->GetGlyphExtents(gid, aExtents); + } + + return gid; +} + gfxFT2LockedFace::CharVariantFunction gfxFT2LockedFace::FindCharVariantFunction() { diff --git a/gfx/thebes/gfxFT2Utils.h b/gfx/thebes/gfxFT2Utils.h index e58821e5fcca..35958bbf794a 100644 --- a/gfx/thebes/gfxFT2Utils.h +++ b/gfx/thebes/gfxFT2Utils.h @@ -46,7 +46,42 @@ public: */ uint32_t GetUVSGlyph(uint32_t aCharCode, uint32_t aVariantSelector); + void GetMetrics(gfxFont::Metrics* aMetrics, uint32_t* aSpaceGlyph); + + // A scale factor for use in converting horizontal metrics from font units + // to pixels. + gfxFloat XScale() + { + if (MOZ_UNLIKELY(!mFace)) + return 0.0; + + const FT_Size_Metrics& ftMetrics = mFace->size->metrics; + + if (FT_IS_SCALABLE(mFace)) { + // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not + // have subpixel accuracy. + // + // FT_Size_Metrics::x_scale is in 16.16 fixed point format. Its + // (fractional) value is a factor that converts vertical metrics + // from design units to units of 1/64 pixels, so that the result + // may be interpreted as pixels in 26.6 fixed point format. + return FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.x_scale)); + } + + // Not scalable. + // FT_Size_Metrics doc says x_scale is "only relevant for scalable + // font formats". + return gfxFloat(ftMetrics.x_ppem) / gfxFloat(mFace->units_per_EM); + } + protected: + /** + * Get extents for a simple character representable by a single glyph. + * The return value is the glyph id of that glyph or zero if no such glyph + * exists. aExtents is only set when this returns a non-zero glyph id. + */ + uint32_t GetCharExtents(char aChar, cairo_text_extents_t* aExtents); + typedef FT_UInt (*CharVariantFunction)(FT_Face face, FT_ULong charcode, FT_ULong variantSelector);