diff options
Diffstat (limited to 'gfx/thebes/gfxFT2FontBase.cpp')
-rw-r--r-- | gfx/thebes/gfxFT2FontBase.cpp | 735 |
1 files changed, 735 insertions, 0 deletions
diff --git a/gfx/thebes/gfxFT2FontBase.cpp b/gfx/thebes/gfxFT2FontBase.cpp new file mode 100644 index 0000000000..ad5fc09bbc --- /dev/null +++ b/gfx/thebes/gfxFT2FontBase.cpp @@ -0,0 +1,735 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxFT2FontBase.h" +#include "gfxFT2Utils.h" +#include "harfbuzz/hb.h" +#include "mozilla/Likely.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "gfxFontConstants.h" +#include "gfxFontUtils.h" +#include <algorithm> +#include <dlfcn.h> + +#include FT_TRUETYPE_TAGS_H +#include FT_TRUETYPE_TABLES_H +#include FT_ADVANCES_H +#include FT_MULTIPLE_MASTERS_H + +#ifndef FT_LOAD_COLOR +# define FT_LOAD_COLOR (1L << 20) +#endif +#ifndef FT_FACE_FLAG_COLOR +# define FT_FACE_FLAG_COLOR (1L << 14) +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxFT2FontBase::gfxFT2FontBase( + const RefPtr<UnscaledFontFreeType>& aUnscaledFont, + RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, gfxFontEntry* aFontEntry, + const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden) + : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault), + mFTFace(std::move(aFTFace)), + mFTLoadFlags(aLoadFlags | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH | + FT_LOAD_COLOR), + mEmbolden(aEmbolden), + mFTSize(1.0) {} + +gfxFT2FontBase::~gfxFT2FontBase() { mFTFace->ForgetLockOwner(this); } + +FT_Face gfxFT2FontBase::LockFTFace() { + if (!mFTFace->Lock(this)) { + FT_Set_Transform(mFTFace->GetFace(), nullptr, nullptr); + + FT_F26Dot6 charSize = NS_lround(mFTSize * 64.0); + FT_Set_Char_Size(mFTFace->GetFace(), charSize, charSize, 0, 0); + } + return mFTFace->GetFace(); +} + +void gfxFT2FontBase::UnlockFTFace() { mFTFace->Unlock(); } + +gfxFT2FontEntryBase::CmapCacheSlot* gfxFT2FontEntryBase::GetCmapCacheSlot( + uint32_t aCharCode) { + // This cache algorithm and size is based on what is done in + // cairo_scaled_font_text_to_glyphs and pango_fc_font_real_get_glyph. I + // think the concept is that adjacent characters probably come mostly from + // one Unicode block. This assumption is probably not so valid with + // scripts with large character sets as used for East Asian languages. + if (!mCmapCache) { + mCmapCache = mozilla::MakeUnique<CmapCacheSlot[]>(kNumCmapCacheSlots); + + // Invalidate slot 0 by setting its char code to something that would + // never end up in slot 0. All other slots are already invalid + // because they have mCharCode = 0 and a glyph for char code 0 will + // always be in the slot 0. + mCmapCache[0].mCharCode = 1; + } + return &mCmapCache[aCharCode % kNumCmapCacheSlots]; +} + +static FT_ULong GetTableSizeFromFTFace(SharedFTFace* aFace, + uint32_t aTableTag) { + if (!aFace) { + return 0; + } + FT_ULong len = 0; + if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, nullptr, &len) != 0) { + return 0; + } + return len; +} + +bool gfxFT2FontEntryBase::FaceHasTable(SharedFTFace* aFace, + uint32_t aTableTag) { + return GetTableSizeFromFTFace(aFace, aTableTag) > 0; +} + +nsresult gfxFT2FontEntryBase::CopyFaceTable(SharedFTFace* aFace, + uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) { + FT_ULong length = GetTableSizeFromFTFace(aFace, aTableTag); + if (!length) { + return NS_ERROR_NOT_AVAILABLE; + } + if (!aBuffer.SetLength(length, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, aBuffer.Elements(), + &length) != 0) { + aBuffer.Clear(); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +uint32_t gfxFT2FontBase::GetGlyph(uint32_t aCharCode) { + // FcFreeTypeCharIndex needs to lock the FT_Face and can end up searching + // through all the postscript glyph names in the font. Therefore use a + // lightweight cache, which is stored on the font entry. + auto* slot = static_cast<gfxFT2FontEntryBase*>(mFontEntry.get()) + ->GetCmapCacheSlot(aCharCode); + if (slot->mCharCode != aCharCode) { + slot->mCharCode = aCharCode; + slot->mGlyphIndex = gfxFT2LockedFace(this).GetGlyph(aCharCode); + } + return slot->mGlyphIndex; +} + +// 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; +} + +static inline gfxRect ScaleGlyphBounds(const IntRect& aBounds, + gfxFloat aScale) { + return gfxRect(FLOAT_FROM_26_6(aBounds.x) * aScale, + FLOAT_FROM_26_6(aBounds.y) * aScale, + FLOAT_FROM_26_6(aBounds.width) * aScale, + FLOAT_FROM_26_6(aBounds.height) * aScale); +} + +/** + * 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. aWidth/aBounds is only set when this returns a non-zero glyph id. + * This is just for use during initialization, and doesn't use the width cache. + */ +uint32_t gfxFT2FontBase::GetCharExtents(char aChar, gfxFloat* aWidth, + gfxRect* aBounds) { + FT_UInt gid = GetGlyph(aChar); + int32_t width; + IntRect bounds; + if (gid && GetFTGlyphExtents(gid, aWidth ? &width : nullptr, + aBounds ? &bounds : nullptr)) { + if (aWidth) { + *aWidth = FLOAT_FROM_16_16(width); + } + if (aBounds) { + *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize); + } + return gid; + } else { + return 0; + } +} + +/** + * Find the closest available fixed strike size, if applicable, to the + * desired font size. + */ +static double FindClosestSize(FT_Face aFace, double aSize) { + // FT size selection does not actually support sizes smaller than 1 and will + // clamp this internally, regardless of what is requested. Do the clamp here + // instead so that glyph extents/font matrix scaling will compensate it, as + // Cairo normally would. + if (aSize < 1.0) { + aSize = 1.0; + } + if (FT_IS_SCALABLE(aFace)) { + return aSize; + } + double bestDist = DBL_MAX; + FT_Int bestSize = -1; + for (FT_Int i = 0; i < aFace->num_fixed_sizes; i++) { + double dist = aFace->available_sizes[i].y_ppem / 64.0 - aSize; + // If the previous best is smaller than the desired size, prefer + // a bigger size. Otherwise, just choose whatever size is closest. + if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) { + bestDist = dist; + bestSize = i; + } + } + if (bestSize < 0) { + return aSize; + } + return aFace->available_sizes[bestSize].y_ppem / 64.0; +} + +void gfxFT2FontBase::InitMetrics() { + mFUnitsConvFactor = 0.0; + + if (MOZ_UNLIKELY(GetStyle()->size <= 0.0) || + MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0)) { + memset(&mMetrics, 0, sizeof(mMetrics)); // zero initialize + mSpaceGlyph = GetGlyph(' '); + return; + } + + // Cairo metrics are normalized to em-space, so that whatever fixed size + // might actually be chosen is factored out. They are then later scaled by + // the font matrix to the target adjusted size. Stash the chosen closest + // size here for later scaling of the metrics. + mFTSize = FindClosestSize(mFTFace->GetFace(), GetAdjustedSize()); + + // Explicitly lock the face so we can release it early before calling + // back into Cairo below. + FT_Face face = LockFTFace(); + + 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.zeroWidth = 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; + } + + const FT_Size_Metrics& ftMetrics = face->size->metrics; + + 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<TT_Header*>(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<TT_OS2*>(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) || + // maxAscent/maxDescent get used for frame heights, and some fonts + // don't have the HHEA table ascent/descent set (bug 279032). + (mMetrics.maxAscent == 0.0 && mMetrics.maxDescent == 0.0)) { + // We use NS_round here to parallel the pixel-rounded values that + // freetype gives us for ftMetrics.ascender/descender. + mMetrics.maxAscent = NS_round(mMetrics.emAscent); + mMetrics.maxDescent = NS_round(mMetrics.emDescent); + } + } else { + mMetrics.emAscent = mMetrics.maxAscent; + mMetrics.emDescent = mMetrics.maxDescent; + } + + // 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<TT_Postscript*>(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); + + 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; + } + + // 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. + mMetrics.aveCharWidth = + ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale); + } else { + mMetrics.aveCharWidth = 0.0; // updated below + } + + if (os2 && os2->sCapHeight && yScale > 0.0) { + mMetrics.capHeight = os2->sCapHeight * yScale; + } else { + mMetrics.capHeight = mMetrics.maxAscent; + } + + // Release the face lock to safely load glyphs with GetCharExtents if + // necessary without recursively locking. + UnlockFTFace(); + + gfxFloat width; + mSpaceGlyph = GetCharExtents(' ', &width); + if (mSpaceGlyph) { + mMetrics.spaceWidth = width; + } else { + mMetrics.spaceWidth = mMetrics.maxAdvance; // guess + } + + if (GetCharExtents('0', &width)) { + mMetrics.zeroWidth = width; + } else { + mMetrics.zeroWidth = -1.0; // indicates not found + } + + // 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. + gfxFloat xWidth; + gfxRect xBounds; + if (GetCharExtents('x', &xWidth, &xBounds) && xBounds.y < 0.0) { + mMetrics.xHeight = -xBounds.y; + mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, xWidth); + } + + if (GetCharExtents('H', nullptr, &xBounds) && xBounds.y < 0.0) { + mMetrics.capHeight = -xBounds.y; + } + + mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, mMetrics.zeroWidth); + if (mMetrics.aveCharWidth == 0.0) { + mMetrics.aveCharWidth = mMetrics.spaceWidth; + } + // Apparently hinting can mean that max_advance is not always accurate. + mMetrics.maxAdvance = std::max(mMetrics.maxAdvance, mMetrics.aveCharWidth); + + 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 + // printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size); + // printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font))); + + fprintf (stderr, "Font: %s\n", NS_ConvertUTF16toUTF8(GetName()).get()); + fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); + fprintf (stderr, " maxAscent: %f maxDescent: %f\n", mMetrics.maxAscent, mMetrics.maxDescent); + fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading); + 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() { + return mMetrics; +} + +uint32_t gfxFT2FontBase::GetGlyph(uint32_t unicode, + uint32_t variation_selector) { + if (variation_selector) { + uint32_t id = + gfxFT2LockedFace(this).GetUVSGlyph(unicode, variation_selector); + if (id) { + return id; + } + unicode = gfxFontUtils::GetUVSFallback(unicode, variation_selector); + if (unicode) { + return GetGlyph(unicode); + } + return 0; + } + + return GetGlyph(unicode); +} + +bool gfxFT2FontBase::ShouldRoundXOffset(cairo_t* aCairo) const { + // Force rounding if outputting to a Cairo context or if requested by pref to + // disable subpixel positioning. Otherwise, allow subpixel positioning (no + // rounding) if rendering a scalable outline font with anti-aliasing. + // Monochrome rendering or some bitmap fonts can become too distorted with + // subpixel positioning, so force rounding in those cases. Also be careful not + // to use subpixel positioning if the user requests full hinting via + // Fontconfig, which we detect by checking that neither hinting was disabled + // nor light hinting was requested. Allow pref to force subpixel positioning + // on even if full hinting was requested. + return MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_disabled_AtStartup()) || + aCairo != nullptr || !mFTFace || !FT_IS_SCALABLE(mFTFace->GetFace()) || + (mFTLoadFlags & FT_LOAD_MONOCHROME) || + !((mFTLoadFlags & FT_LOAD_NO_HINTING) || + FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT || + MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_enabled_AtStartup())); +} + +FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) { + FT_Vector strength = {0, 0}; + if (!mEmbolden) { + return strength; + } + + // If it's an outline glyph, we'll be using mozilla_glyphslot_embolden_less + // (see gfx/wr/webrender/src/platform/unix/font.rs), so we need to match its + // emboldening strength here. + if (aFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + strength.x = + FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 48; + strength.y = strength.x; + return strength; + } + + // This is the embolden "strength" used by FT_GlyphSlot_Embolden. + strength.x = + FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 24; + strength.y = strength.x; + if (aFace->glyph->format == FT_GLYPH_FORMAT_BITMAP) { + strength.x &= -64; + if (!strength.x) { + strength.x = 64; + } + strength.y &= -64; + } + return strength; +} + +bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance, + IntRect* aBounds) { + gfxFT2LockedFace face(this); + MOZ_ASSERT(face.get()); + if (!face.get()) { + // Failed to get the FT_Face? Give up already. + NS_WARNING("failed to get FT_Face!"); + return false; + } + + FT_Int32 flags = mFTLoadFlags; + if (!aBounds) { + flags |= FT_LOAD_ADVANCE_ONLY; + } + if (Factory::LoadFTGlyph(face.get(), aGID, flags) != FT_Err_Ok) { + // FT_Face was somehow broken/invalid? Don't try to access glyph slot. + // This probably shouldn't happen, but does: see bug 1440938. + NS_WARNING("failed to load glyph!"); + return false; + } + + bool hintMetrics = ShouldHintMetrics(); + + // Normalize out the loaded FT glyph size and then scale to the actually + // desired size, in case these two sizes differ. + gfxFloat extentsScale = GetAdjustedSize() / mFTSize; + + FT_Vector bold = GetEmboldenStrength(face.get()); + + // Due to freetype bug 52683 we MUST use the linearHoriAdvance field when + // dealing with a variation font; also use it for scalable fonts when not + // applying hinting. Otherwise, prefer hinted width from glyph->advance.x. + if (aAdvance) { + FT_Fixed advance; + if (!ShouldRoundXOffset(nullptr) || FT_HAS_MULTIPLE_MASTERS(face.get())) { + advance = face.get()->glyph->linearHoriAdvance; + } else { + advance = face.get()->glyph->advance.x << 10; // convert 26.6 to 16.16 + } + if (advance) { + advance += bold.x << 10; // convert 26.6 to 16.16 + } + // Hinting was requested, but FT did not apply any hinting to the metrics. + // Round the advance here to approximate hinting as Cairo does. This must + // happen BEFORE we apply the glyph extents scale, just like FT hinting + // would. + if (hintMetrics && (mFTLoadFlags & FT_LOAD_NO_HINTING)) { + advance = (advance + 0x8000) & 0xffff0000u; + } + *aAdvance = NS_lround(advance * extentsScale); + } + + if (aBounds) { + const FT_Glyph_Metrics& metrics = face.get()->glyph->metrics; + FT_F26Dot6 x = metrics.horiBearingX; + FT_F26Dot6 y = -metrics.horiBearingY; + FT_F26Dot6 x2 = x + metrics.width; + FT_F26Dot6 y2 = y + metrics.height; + // Synthetic bold moves the glyph top and right boundaries. + y -= bold.y; + x2 += bold.x; + if (hintMetrics && (mFTLoadFlags & FT_LOAD_NO_HINTING)) { + x &= -64; + y &= -64; + x2 = (x2 + 63) & -64; + y2 = (y2 + 63) & -64; + } + *aBounds = IntRect(x, y, x2 - x, y2 - y); + } + return true; +} + +/** + * Get the cached glyph metrics for the glyph id if available. Otherwise, query + * FreeType for the glyph extents and initialize the glyph metrics. + */ +const gfxFT2FontBase::GlyphMetrics& gfxFT2FontBase::GetCachedGlyphMetrics( + uint16_t aGID, IntRect* aBounds) { + if (!mGlyphMetrics) { + mGlyphMetrics = + mozilla::MakeUnique<nsDataHashtable<nsUint32HashKey, GlyphMetrics>>( + 128); + } + + if (const GlyphMetrics* metrics = mGlyphMetrics->GetValue(aGID)) { + return *metrics; + } + + GlyphMetrics& metrics = mGlyphMetrics->GetOrInsert(aGID); + IntRect bounds; + if (GetFTGlyphExtents(aGID, &metrics.mAdvance, &bounds)) { + metrics.SetBounds(bounds); + if (aBounds) { + *aBounds = bounds; + } + } + return metrics; +} + +int32_t gfxFT2FontBase::GetGlyphWidth(uint16_t aGID) { + return GetCachedGlyphMetrics(aGID).mAdvance; +} + +bool gfxFT2FontBase::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, + bool aTight) { + IntRect bounds; + const GlyphMetrics& metrics = GetCachedGlyphMetrics(aGID, &bounds); + if (!metrics.HasValidBounds()) { + return false; + } + // Check if there are cached bounds and use those if available. Otherwise, + // fall back to directly querying the glyph extents. + if (metrics.HasCachedBounds()) { + bounds = metrics.GetBounds(); + } else if (bounds.IsEmpty() && !GetFTGlyphExtents(aGID, nullptr, &bounds)) { + return false; + } + // The bounds are stored unscaled, so must be scaled to the adjusted size. + *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize); + return true; +} + +// For variation fonts, figure out the variation coordinates to be applied +// for each axis, in freetype's order (which may not match the order of +// axes in mStyle.variationSettings, so we need to search by axis tag). +/*static*/ +void gfxFT2FontBase::SetupVarCoords( + FT_MM_Var* aMMVar, const nsTArray<gfxFontVariation>& aVariations, + FT_Face aFTFace) { + if (!aMMVar) { + return; + } + + nsTArray<FT_Fixed> coords; + for (unsigned i = 0; i < aMMVar->num_axis; ++i) { + coords.AppendElement(aMMVar->axis[i].def); + for (const auto& v : aVariations) { + if (aMMVar->axis[i].tag == v.mTag) { + FT_Fixed val = v.mValue * 0x10000; + val = std::min(val, aMMVar->axis[i].maximum); + val = std::max(val, aMMVar->axis[i].minimum); + coords[i] = val; + break; + } + } + } + + if (!coords.IsEmpty()) { +#if MOZ_TREE_FREETYPE + FT_Set_Var_Design_Coordinates(aFTFace, coords.Length(), coords.Elements()); +#else + typedef FT_Error (*SetCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*); + static SetCoordsFunc setCoords; + static bool firstTime = true; + if (firstTime) { + firstTime = false; + setCoords = + (SetCoordsFunc)dlsym(RTLD_DEFAULT, "FT_Set_Var_Design_Coordinates"); + } + if (setCoords) { + (*setCoords)(aFTFace, coords.Length(), coords.Elements()); + } +#endif + } +} + +already_AddRefed<SharedFTFace> FTUserFontData::CloneFace(int aFaceIndex) { + RefPtr<SharedFTFace> face = Factory::NewSharedFTFaceFromData( + nullptr, mFontData, mLength, aFaceIndex, this); + if (!face || + (FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) != FT_Err_Ok && + FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL) != + FT_Err_Ok)) { + return nullptr; + } + return face.forget(); +} |