gfxFT2FontBase.cpp (32456B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "gfxFT2FontBase.h" 7 #include "gfxFT2Utils.h" 8 #include "harfbuzz/hb.h" 9 #include "mozilla/Likely.h" 10 #include "mozilla/StaticPrefs_gfx.h" 11 #include "gfxFontConstants.h" 12 #include "gfxFontUtils.h" 13 #include "gfxHarfBuzzShaper.h" 14 #include <algorithm> 15 #include <dlfcn.h> 16 17 #include FT_TRUETYPE_TAGS_H 18 #include FT_TRUETYPE_TABLES_H 19 #include FT_ADVANCES_H 20 #include FT_MULTIPLE_MASTERS_H 21 22 #ifndef FT_LOAD_COLOR 23 # define FT_LOAD_COLOR (1L << 20) 24 #endif 25 #ifndef FT_FACE_FLAG_COLOR 26 # define FT_FACE_FLAG_COLOR (1L << 14) 27 #endif 28 29 using namespace mozilla; 30 using namespace mozilla::gfx; 31 32 gfxFT2FontBase::gfxFT2FontBase( 33 const RefPtr<UnscaledFontFreeType>& aUnscaledFont, 34 RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, gfxFontEntry* aFontEntry, 35 const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden) 36 : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault), 37 mFTFace(std::move(aFTFace)), 38 mFTLoadFlags(aLoadFlags | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH | 39 FT_LOAD_COLOR), 40 mEmbolden(aEmbolden), 41 mFTSize(0.0) {} 42 43 gfxFT2FontBase::~gfxFT2FontBase() { mFTFace->ForgetLockOwner(this); } 44 45 FT_Face gfxFT2FontBase::LockFTFace() const 46 MOZ_CAPABILITY_ACQUIRE(mFTFace) MOZ_NO_THREAD_SAFETY_ANALYSIS { 47 if (!mFTFace->Lock(this)) { 48 FT_Set_Transform(mFTFace->GetFace(), nullptr, nullptr); 49 50 FT_F26Dot6 charSize = NS_lround(mFTSize * 64.0); 51 FT_Set_Char_Size(mFTFace->GetFace(), charSize, charSize, 0, 0); 52 } 53 return mFTFace->GetFace(); 54 } 55 56 void gfxFT2FontBase::UnlockFTFace() const 57 MOZ_CAPABILITY_RELEASE(mFTFace) MOZ_NO_THREAD_SAFETY_ANALYSIS { 58 mFTFace->Unlock(); 59 } 60 61 static FT_ULong GetTableSizeFromFTFace(SharedFTFace* aFace, 62 uint32_t aTableTag) { 63 if (!aFace) { 64 return 0; 65 } 66 FT_ULong len = 0; 67 if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, nullptr, &len) != 0) { 68 return 0; 69 } 70 return len; 71 } 72 73 bool gfxFT2FontEntryBase::FaceHasTable(SharedFTFace* aFace, 74 uint32_t aTableTag) { 75 return GetTableSizeFromFTFace(aFace, aTableTag) > 0; 76 } 77 78 nsresult gfxFT2FontEntryBase::CopyFaceTable(SharedFTFace* aFace, 79 uint32_t aTableTag, 80 nsTArray<uint8_t>& aBuffer) { 81 FT_ULong length = GetTableSizeFromFTFace(aFace, aTableTag); 82 if (!length) { 83 return NS_ERROR_NOT_AVAILABLE; 84 } 85 if (!aBuffer.SetLength(length, fallible)) { 86 return NS_ERROR_OUT_OF_MEMORY; 87 } 88 if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, aBuffer.Elements(), 89 &length) != 0) { 90 aBuffer.Clear(); 91 return NS_ERROR_FAILURE; 92 } 93 return NS_OK; 94 } 95 96 uint32_t gfxFT2FontEntryBase::GetGlyph(uint32_t aCharCode, 97 gfxFT2FontBase* aFont) { 98 const uint32_t slotIndex = aCharCode % kNumCmapCacheSlots; 99 { 100 // Try to read a cached entry without taking an exclusive lock. 101 AutoReadLock lock(mLock); 102 if (mCmapCache) { 103 const auto& slot = mCmapCache[slotIndex]; 104 if (slot.mCharCode == aCharCode) { 105 return slot.mGlyphIndex; 106 } 107 } 108 } 109 110 // Create/update the charcode-to-glyphid cache. 111 AutoWriteLock lock(mLock); 112 113 // This cache algorithm and size is based on what is done in 114 // cairo_scaled_font_text_to_glyphs and pango_fc_font_real_get_glyph. I 115 // think the concept is that adjacent characters probably come mostly from 116 // one Unicode block. This assumption is probably not so valid with 117 // scripts with large character sets as used for East Asian languages. 118 if (!mCmapCache) { 119 mCmapCache = mozilla::MakeUnique<CmapCacheSlot[]>(kNumCmapCacheSlots); 120 121 // Invalidate slot 0 by setting its char code to something that would 122 // never end up in slot 0. All other slots are already invalid 123 // because they have mCharCode = 0 and a glyph for char code 0 will 124 // always be in the slot 0. 125 mCmapCache[0].mCharCode = 1; 126 } 127 128 auto& slot = mCmapCache[slotIndex]; 129 if (slot.mCharCode != aCharCode) { 130 slot.mCharCode = aCharCode; 131 slot.mGlyphIndex = gfxFT2LockedFace(aFont).GetGlyph(aCharCode); 132 } 133 return slot.mGlyphIndex; 134 } 135 136 size_t gfxFT2FontEntryBase::ComputedSizeOfExcludingThis( 137 MallocSizeOf aMallocSizeOf) { 138 size_t result = gfxFontEntry::ComputedSizeOfExcludingThis(aMallocSizeOf); 139 140 if (const auto* data = GetUserFontData()) { 141 if (data->FontData()) { 142 result += aMallocSizeOf(data->FontData()); 143 } 144 } 145 146 return result; 147 } 148 149 // aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics 150 static inline FT_Long ScaleRoundDesignUnits(FT_Short aDesignMetric, 151 FT_Fixed aScale) { 152 FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale); 153 return ROUND_26_6_TO_INT(fixed26dot6); 154 } 155 156 // Snap a line to pixels while keeping the center and size of the line as 157 // close to the original position as possible. 158 // 159 // Pango does similar snapping for underline and strikethrough when fonts are 160 // hinted, but nsCSSRendering::GetTextDecorationRectInternal always snaps the 161 // top and size of lines. Optimizing the distance between the line and 162 // baseline is probably good for the gap between text and underline, but 163 // optimizing the center of the line is better for positioning strikethough. 164 static void SnapLineToPixels(gfxFloat& aOffset, gfxFloat& aSize) { 165 gfxFloat snappedSize = std::max(floor(aSize + 0.5), 1.0); 166 // Correct offset for change in size 167 gfxFloat offset = aOffset - 0.5 * (aSize - snappedSize); 168 // Snap offset 169 aOffset = floor(offset + 0.5); 170 aSize = snappedSize; 171 } 172 173 static inline gfxRect ScaleGlyphBounds(const IntRect& aBounds, 174 gfxFloat aScale) { 175 return gfxRect(FLOAT_FROM_26_6(aBounds.x) * aScale, 176 FLOAT_FROM_26_6(aBounds.y) * aScale, 177 FLOAT_FROM_26_6(aBounds.width) * aScale, 178 FLOAT_FROM_26_6(aBounds.height) * aScale); 179 } 180 181 /** 182 * Get extents for a simple character representable by a single glyph. 183 * The return value is the glyph id of that glyph or zero if no such glyph 184 * exists. aWidth/aBounds is only set when this returns a non-zero glyph id. 185 * This is just for use during initialization, and doesn't use the width cache. 186 */ 187 uint32_t gfxFT2FontBase::GetCharExtents(uint32_t aChar, gfxFloat* aWidth, 188 gfxRect* aBounds) { 189 FT_UInt gid = GetGlyph(aChar); 190 int32_t width; 191 IntRect bounds; 192 if (gid && GetFTGlyphExtents(gid, aWidth ? &width : nullptr, 193 aBounds ? &bounds : nullptr)) { 194 if (aWidth) { 195 *aWidth = FLOAT_FROM_16_16(width); 196 } 197 if (aBounds) { 198 *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize); 199 } 200 return gid; 201 } else { 202 return 0; 203 } 204 } 205 206 /** 207 * Find the closest available fixed strike size, if applicable, to the 208 * desired font size. 209 */ 210 static double FindClosestSize(FT_Face aFace, double aSize) { 211 // FT size selection does not actually support sizes smaller than 1 and will 212 // clamp this internally, regardless of what is requested. Do the clamp here 213 // instead so that glyph extents/font matrix scaling will compensate it, as 214 // Cairo normally would. 215 if (aSize < 1.0) { 216 aSize = 1.0; 217 } 218 if (FT_IS_SCALABLE(aFace)) { 219 return aSize; 220 } 221 double bestDist = DBL_MAX; 222 FT_Int bestSize = -1; 223 for (FT_Int i = 0; i < aFace->num_fixed_sizes; i++) { 224 double dist = aFace->available_sizes[i].y_ppem / 64.0 - aSize; 225 // If the previous best is smaller than the desired size, prefer 226 // a bigger size. Otherwise, just choose whatever size is closest. 227 if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) { 228 bestDist = dist; 229 bestSize = i; 230 } 231 } 232 if (bestSize < 0) { 233 return aSize; 234 } 235 return aFace->available_sizes[bestSize].y_ppem / 64.0; 236 } 237 238 void gfxFT2FontBase::InitMetrics() { 239 mFUnitsConvFactor = 0.0; 240 241 if (MOZ_UNLIKELY(mStyle.AdjustedSizeMustBeZero())) { 242 memset(&mMetrics, 0, sizeof(mMetrics)); // zero initialize 243 mSpaceGlyph = GetGlyph(' '); 244 return; 245 } 246 247 if (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) != 248 FontSizeAdjust::Tag::None && 249 mStyle.sizeAdjust >= 0.0 && GetAdjustedSize() > 0.0 && mFTSize == 0.0) { 250 // If font-size-adjust is in effect, we need to get metrics in order to 251 // determine the aspect ratio, then compute the final adjusted size and 252 // re-initialize metrics. 253 // Setting mFTSize nonzero here ensures we will not recurse again; the 254 // actual value will be overridden by FindClosestSize below. 255 mFTSize = 1.0; 256 InitMetrics(); 257 // Now do the font-size-adjust calculation and set the final size. 258 gfxFloat aspect; 259 switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) { 260 default: 261 MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?"); 262 aspect = 0.0; 263 break; 264 case FontSizeAdjust::Tag::ExHeight: 265 aspect = mMetrics.xHeight / mAdjustedSize; 266 break; 267 case FontSizeAdjust::Tag::CapHeight: 268 aspect = mMetrics.capHeight / mAdjustedSize; 269 break; 270 case FontSizeAdjust::Tag::ChWidth: 271 aspect = 272 mMetrics.zeroWidth > 0.0 ? mMetrics.zeroWidth / mAdjustedSize : 0.5; 273 break; 274 case FontSizeAdjust::Tag::IcWidth: 275 case FontSizeAdjust::Tag::IcHeight: { 276 bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) == 277 FontSizeAdjust::Tag::IcHeight; 278 gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical); 279 aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0; 280 break; 281 } 282 } 283 if (aspect > 0.0) { 284 // If we created a shaper above (to measure glyphs), discard it so we 285 // get a new one for the adjusted scaling. 286 delete mHarfBuzzShaper.exchange(nullptr); 287 mAdjustedSize = mStyle.GetAdjustedSize(aspect); 288 // Ensure the FT_Face will be reconfigured for the new size next time we 289 // need to use it. 290 mFTFace->ForgetLockOwner(this); 291 } 292 } 293 294 // Set mAdjustedSize if it hasn't already been set by a font-size-adjust 295 // computation. 296 mAdjustedSize = GetAdjustedSize(); 297 298 // Cairo metrics are normalized to em-space, so that whatever fixed size 299 // might actually be chosen is factored out. They are then later scaled by 300 // the font matrix to the target adjusted size. Stash the chosen closest 301 // size here for later scaling of the metrics. 302 mFTSize = FindClosestSize(mFTFace->GetFace(), GetAdjustedSize()); 303 304 // Explicitly lock the face so we can release it early before calling 305 // back into Cairo below. 306 FT_Face face = LockFTFace(); 307 308 if (MOZ_UNLIKELY(!face)) { 309 // No face. This unfortunate situation might happen if the font 310 // file is (re)moved at the wrong time. 311 const gfxFloat emHeight = GetAdjustedSize(); 312 mMetrics.emHeight = emHeight; 313 mMetrics.maxAscent = mMetrics.emAscent = 0.8 * emHeight; 314 mMetrics.maxDescent = mMetrics.emDescent = 0.2 * emHeight; 315 mMetrics.maxHeight = emHeight; 316 mMetrics.internalLeading = 0.0; 317 mMetrics.externalLeading = 0.2 * emHeight; 318 const gfxFloat spaceWidth = 0.5 * emHeight; 319 mMetrics.spaceWidth = spaceWidth; 320 mMetrics.maxAdvance = spaceWidth; 321 mMetrics.aveCharWidth = spaceWidth; 322 mMetrics.zeroWidth = spaceWidth; 323 mMetrics.ideographicWidth = emHeight; 324 const gfxFloat xHeight = 0.5 * emHeight; 325 mMetrics.xHeight = xHeight; 326 mMetrics.capHeight = mMetrics.maxAscent; 327 const gfxFloat underlineSize = emHeight / 14.0; 328 mMetrics.underlineSize = underlineSize; 329 mMetrics.underlineOffset = -underlineSize; 330 mMetrics.strikeoutOffset = 0.25 * emHeight; 331 mMetrics.strikeoutSize = underlineSize; 332 333 SanitizeMetrics(&mMetrics, false); 334 UnlockFTFace(); 335 return; 336 } 337 338 const FT_Size_Metrics& ftMetrics = face->size->metrics; 339 340 mMetrics.maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender); 341 mMetrics.maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender); 342 mMetrics.maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance); 343 gfxFloat lineHeight = FLOAT_FROM_26_6(ftMetrics.height); 344 345 gfxFloat emHeight; 346 // Scale for vertical design metric conversion: pixels per design unit. 347 // If this remains at 0.0, we can't use metrics from OS/2 etc. 348 gfxFloat yScale = 0.0; 349 if (FT_IS_SCALABLE(face)) { 350 // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not 351 // have subpixel accuracy. 352 // 353 // FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its 354 // (fractional) value is a factor that converts vertical metrics from 355 // design units to units of 1/64 pixels, so that the result may be 356 // interpreted as pixels in 26.6 fixed point format. 357 mFUnitsConvFactor = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.x_scale)); 358 yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale)); 359 emHeight = face->units_per_EM * yScale; 360 } else { // Not scalable. 361 emHeight = ftMetrics.y_ppem; 362 // FT_Face doc says units_per_EM and a bunch of following fields 363 // are "only relevant to scalable outlines". If it's an sfnt, 364 // we can get units_per_EM from the 'head' table instead; otherwise, 365 // we don't have a unitsPerEm value so we can't compute/use yScale or 366 // mFUnitsConvFactor (x scale). 367 if (const TT_Header* head = 368 static_cast<TT_Header*>(FT_Get_Sfnt_Table(face, ft_sfnt_head))) { 369 gfxFloat emUnit = head->Units_Per_EM; 370 mFUnitsConvFactor = ftMetrics.x_ppem / emUnit; 371 // Bug 1267909 - Even if the font is not explicitly scalable, 372 // if the face has color bitmaps, it should be treated as scalable 373 // and scaled to the desired size. Metrics based on y_ppem need 374 // to be rescaled for the adjusted size. This makes metrics agree 375 // with the scales we pass to Cairo for Fontconfig fonts. 376 if (face->face_flags & FT_FACE_FLAG_COLOR) { 377 emHeight = GetAdjustedSize(); 378 gfxFloat adjustScale = emHeight / ftMetrics.y_ppem; 379 mMetrics.maxAscent *= adjustScale; 380 mMetrics.maxDescent *= adjustScale; 381 mMetrics.maxAdvance *= adjustScale; 382 lineHeight *= adjustScale; 383 mFUnitsConvFactor *= adjustScale; 384 } 385 yScale = emHeight / emUnit; 386 } 387 } 388 389 TT_OS2* os2 = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(face, ft_sfnt_os2)); 390 391 if (os2 && os2->sTypoAscender && yScale > 0.0) { 392 mMetrics.emAscent = os2->sTypoAscender * yScale; 393 mMetrics.emDescent = -os2->sTypoDescender * yScale; 394 FT_Short typoHeight = 395 os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap; 396 lineHeight = typoHeight * yScale; 397 398 // If the OS/2 fsSelection USE_TYPO_METRICS bit is set, 399 // set maxAscent/Descent from the sTypo* fields instead of hhea. 400 const uint16_t kUseTypoMetricsMask = 1 << 7; 401 if ((os2->fsSelection & kUseTypoMetricsMask) || 402 // maxAscent/maxDescent get used for frame heights, and some fonts 403 // don't have the HHEA table ascent/descent set (bug 279032). 404 (mMetrics.maxAscent == 0.0 && mMetrics.maxDescent == 0.0)) { 405 // We use NS_round here to parallel the pixel-rounded values that 406 // freetype gives us for ftMetrics.ascender/descender. 407 mMetrics.maxAscent = NS_round(mMetrics.emAscent); 408 mMetrics.maxDescent = NS_round(mMetrics.emDescent); 409 } 410 } else { 411 mMetrics.emAscent = mMetrics.maxAscent; 412 mMetrics.emDescent = mMetrics.maxDescent; 413 } 414 415 // gfxFont::Metrics::underlineOffset is the position of the top of the 416 // underline. 417 // 418 // FT_FaceRec documentation describes underline_position as "the 419 // center of the underlining stem". This was the original definition 420 // of the PostScript metric, but in the PostScript table of OpenType 421 // fonts the metric is "the top of the underline" 422 // (http://www.microsoft.com/typography/otspec/post.htm), and FreeType 423 // (up to version 2.3.7) doesn't make any adjustment. 424 // 425 // Therefore get the underline position directly from the table 426 // ourselves when this table exists. Use FreeType's metrics for 427 // other (including older PostScript) fonts. 428 if (face->underline_position && face->underline_thickness && yScale > 0.0) { 429 mMetrics.underlineSize = face->underline_thickness * yScale; 430 TT_Postscript* post = 431 static_cast<TT_Postscript*>(FT_Get_Sfnt_Table(face, ft_sfnt_post)); 432 if (post && post->underlinePosition) { 433 mMetrics.underlineOffset = post->underlinePosition * yScale; 434 } else { 435 mMetrics.underlineOffset = 436 face->underline_position * yScale + 0.5 * mMetrics.underlineSize; 437 } 438 } else { // No underline info. 439 // Imitate Pango. 440 mMetrics.underlineSize = emHeight / 14.0; 441 mMetrics.underlineOffset = -mMetrics.underlineSize; 442 } 443 444 if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) { 445 mMetrics.strikeoutSize = os2->yStrikeoutSize * yScale; 446 mMetrics.strikeoutOffset = os2->yStrikeoutPosition * yScale; 447 } else { // No strikeout info. 448 mMetrics.strikeoutSize = mMetrics.underlineSize; 449 // Use OpenType spec's suggested position for Roman font. 450 mMetrics.strikeoutOffset = 451 emHeight * 409.0 / 2048.0 + 0.5 * mMetrics.strikeoutSize; 452 } 453 SnapLineToPixels(mMetrics.strikeoutOffset, mMetrics.strikeoutSize); 454 455 if (os2 && os2->sxHeight && yScale > 0.0) { 456 mMetrics.xHeight = os2->sxHeight * yScale; 457 } else { 458 // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is 459 // impossible or impractical to determine the x-height, a value of 460 // 0.5em should be used." 461 mMetrics.xHeight = 0.5 * emHeight; 462 } 463 464 // aveCharWidth is used for the width of text input elements so be 465 // liberal rather than conservative in the estimate. 466 if (os2 && os2->xAvgCharWidth) { 467 // Round to pixels as this is compared with maxAdvance to guess 468 // whether this is a fixed width font. 469 mMetrics.aveCharWidth = 470 ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale); 471 } else { 472 mMetrics.aveCharWidth = 0.0; // updated below 473 } 474 475 if (os2 && os2->sCapHeight && yScale > 0.0) { 476 mMetrics.capHeight = os2->sCapHeight * yScale; 477 } else { 478 mMetrics.capHeight = mMetrics.maxAscent; 479 } 480 481 // Release the face lock to safely load glyphs with GetCharExtents if 482 // necessary without recursively locking. 483 UnlockFTFace(); 484 485 gfxFloat width; 486 mSpaceGlyph = GetCharExtents(' ', &width); 487 if (mSpaceGlyph) { 488 mMetrics.spaceWidth = width; 489 } else { 490 mMetrics.spaceWidth = mMetrics.maxAdvance; // guess 491 } 492 493 if (GetCharExtents('0', &width)) { 494 mMetrics.zeroWidth = width; 495 } else { 496 mMetrics.zeroWidth = -1.0; // indicates not found 497 } 498 499 if (GetCharExtents(kWaterIdeograph, &width)) { 500 mMetrics.ideographicWidth = width; 501 } else { 502 mMetrics.ideographicWidth = -1.0; 503 } 504 505 // If we didn't get a usable x-height or cap-height above, try measuring 506 // specific glyphs. This can be affected by hinting, leading to erratic 507 // behavior across font sizes and system configuration, so we prefer to 508 // use the metrics directly from the font if possible. 509 // Using glyph bounds for x-height or cap-height may not really be right, 510 // if fonts have fancy swashes etc. For x-height, CSS 2.1 suggests possibly 511 // using the height of an "o", which may be more consistent across fonts, 512 // but then curve-overshoot should also be accounted for. 513 gfxFloat xWidth; 514 gfxRect xBounds; 515 if (mMetrics.xHeight == 0.0) { 516 if (GetCharExtents('x', &xWidth, &xBounds) && xBounds.y < 0.0) { 517 mMetrics.xHeight = -xBounds.y; 518 mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, xWidth); 519 } 520 } 521 522 if (mMetrics.capHeight == 0.0) { 523 if (GetCharExtents('H', nullptr, &xBounds) && xBounds.y < 0.0) { 524 mMetrics.capHeight = -xBounds.y; 525 } 526 } 527 528 mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, mMetrics.zeroWidth); 529 if (mMetrics.aveCharWidth == 0.0) { 530 mMetrics.aveCharWidth = mMetrics.spaceWidth; 531 } 532 // Apparently hinting can mean that max_advance is not always accurate. 533 mMetrics.maxAdvance = std::max(mMetrics.maxAdvance, mMetrics.aveCharWidth); 534 535 mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent; 536 537 // Make the line height an integer number of pixels so that lines will be 538 // equally spaced (rather than just being snapped to pixels, some up and 539 // some down). Layout calculates line height from the emHeight + 540 // internalLeading + externalLeading, but first each of these is rounded 541 // to layout units. To ensure that the result is an integer number of 542 // pixels, round each of the components to pixels. 543 mMetrics.emHeight = floor(emHeight + 0.5); 544 545 // maxHeight will normally be an integer, but round anyway in case 546 // FreeType is configured differently. 547 mMetrics.internalLeading = 548 floor(mMetrics.maxHeight - mMetrics.emHeight + 0.5); 549 550 // Text input boxes currently don't work well with lineHeight 551 // significantly less than maxHeight (with Verdana, for example). 552 lineHeight = floor(std::max(lineHeight, mMetrics.maxHeight) + 0.5); 553 mMetrics.externalLeading = 554 lineHeight - mMetrics.internalLeading - mMetrics.emHeight; 555 556 // Ensure emAscent + emDescent == emHeight 557 gfxFloat sum = mMetrics.emAscent + mMetrics.emDescent; 558 mMetrics.emAscent = 559 sum > 0.0 ? mMetrics.emAscent * mMetrics.emHeight / sum : 0.0; 560 mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent; 561 562 SanitizeMetrics(&mMetrics, false); 563 564 #if 0 565 // printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size); 566 // printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font))); 567 568 fprintf (stderr, "Font: %s\n", GetName().get()); 569 fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); 570 fprintf (stderr, " maxAscent: %f maxDescent: %f\n", mMetrics.maxAscent, mMetrics.maxDescent); 571 fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading); 572 fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight); 573 fprintf (stderr, " ideographicWidth: %f\n", mMetrics.ideographicWidth); 574 fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize); 575 #endif 576 } 577 578 uint32_t gfxFT2FontBase::GetGlyph(uint32_t unicode, 579 uint32_t variation_selector) { 580 if (variation_selector) { 581 uint32_t id = 582 gfxFT2LockedFace(this).GetUVSGlyph(unicode, variation_selector); 583 if (id) { 584 return id; 585 } 586 unicode = gfxFontUtils::GetUVSFallback(unicode, variation_selector); 587 if (unicode) { 588 return GetGlyph(unicode); 589 } 590 return 0; 591 } 592 593 return GetGlyph(unicode); 594 } 595 596 bool gfxFT2FontBase::ShouldRoundXOffset(cairo_t* aCairo) const { 597 // Force rounding if outputting to a Cairo context or if requested by pref to 598 // disable subpixel positioning. Otherwise, allow subpixel positioning (no 599 // rounding) if rendering a scalable outline font with anti-aliasing. 600 // Monochrome rendering or some bitmap fonts can become too distorted with 601 // subpixel positioning, so force rounding in those cases. Also be careful not 602 // to use subpixel positioning if the user requests full hinting via 603 // Fontconfig, which we detect by checking that neither hinting was disabled 604 // nor light hinting was requested. Allow pref to force subpixel positioning 605 // on even if full hinting was requested. 606 return MOZ_UNLIKELY( 607 StaticPrefs:: 608 gfx_text_subpixel_position_force_disabled_AtStartup()) || 609 aCairo != nullptr || !mFTFace || !FT_IS_SCALABLE(mFTFace->GetFace()) || 610 (mFTLoadFlags & FT_LOAD_MONOCHROME) || 611 !((mFTLoadFlags & FT_LOAD_NO_HINTING) || 612 FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT || 613 MOZ_UNLIKELY( 614 StaticPrefs:: 615 gfx_text_subpixel_position_force_enabled_AtStartup())); 616 } 617 618 FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) const { 619 FT_Vector strength = {0, 0}; 620 if (!mEmbolden) { 621 return strength; 622 } 623 624 // If it's an outline glyph, we'll be using mozilla_glyphslot_embolden_less 625 // (see gfx/wr/webrender/src/platform/unix/font.rs), so we need to match its 626 // emboldening strength here. 627 if (aFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { 628 strength.x = 629 FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 48; 630 strength.y = strength.x; 631 return strength; 632 } 633 634 // This is the embolden "strength" used by FT_GlyphSlot_Embolden. 635 strength.x = 636 FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 24; 637 strength.y = strength.x; 638 if (aFace->glyph->format == FT_GLYPH_FORMAT_BITMAP) { 639 strength.x &= -64; 640 if (!strength.x) { 641 strength.x = 64; 642 } 643 strength.y &= -64; 644 } 645 return strength; 646 } 647 648 bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance, 649 IntRect* aBounds) { 650 gfxFT2LockedFace face(this); 651 MOZ_ASSERT(face.get()); 652 if (!face.get()) { 653 // Failed to get the FT_Face? Give up already. 654 NS_WARNING("failed to get FT_Face!"); 655 return false; 656 } 657 658 FT_Int32 flags = mFTLoadFlags; 659 if (!aBounds) { 660 flags |= FT_LOAD_ADVANCE_ONLY; 661 } 662 663 // Whether to disable subpixel positioning 664 bool roundX = ShouldRoundXOffset(nullptr); 665 666 // Workaround for FT_Load_Glyph not setting linearHoriAdvance for SVG glyphs. 667 // See https://gitlab.freedesktop.org/freetype/freetype/-/issues/1156. 668 if (!roundX && 669 GetFontEntry()->HasFontTable(TRUETYPE_TAG('S', 'V', 'G', ' '))) { 670 flags &= ~FT_LOAD_COLOR; 671 } 672 673 if (Factory::LoadFTGlyph(face.get(), aGID, flags) != FT_Err_Ok) { 674 // FT_Face was somehow broken/invalid? Don't try to access glyph slot. 675 // This probably shouldn't happen, but does: see bug 1440938. 676 NS_WARNING("failed to load glyph!"); 677 return false; 678 } 679 680 // Whether to interpret hinting settings (i.e. not printing) 681 bool hintMetrics = ShouldHintMetrics(); 682 // No hinting disables X and Y hinting. Light disables only X hinting. 683 bool unhintedY = (mFTLoadFlags & FT_LOAD_NO_HINTING) != 0; 684 bool unhintedX = 685 unhintedY || FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT; 686 687 // Normalize out the loaded FT glyph size and then scale to the actually 688 // desired size, in case these two sizes differ. 689 gfxFloat extentsScale = GetAdjustedSize() / mFTSize; 690 691 FT_Vector bold = GetEmboldenStrength(face.get()); 692 693 // Due to freetype bug 52683 we MUST use the linearHoriAdvance field when 694 // dealing with a variation font; also use it for scalable fonts when not 695 // applying hinting. Otherwise, prefer hinted width from glyph->advance.x. 696 if (aAdvance) { 697 FT_Fixed advance; 698 if (!roundX || FT_HAS_MULTIPLE_MASTERS(face.get())) { 699 advance = face.get()->glyph->linearHoriAdvance; 700 } else { 701 advance = face.get()->glyph->advance.x << 10; // convert 26.6 to 16.16 702 } 703 if (advance) { 704 advance += bold.x << 10; // convert 26.6 to 16.16 705 } 706 // Hinting was requested, but FT did not apply any hinting to the metrics. 707 // Round the advance here to approximate hinting as Cairo does. This must 708 // happen BEFORE we apply the glyph extents scale, just like FT hinting 709 // would. 710 if (hintMetrics && roundX && unhintedX) { 711 advance = (advance + 0x8000) & 0xffff0000u; 712 } 713 *aAdvance = NS_lround(advance * extentsScale); 714 } 715 716 if (aBounds) { 717 const FT_Glyph_Metrics& metrics = face.get()->glyph->metrics; 718 FT_F26Dot6 x = metrics.horiBearingX; 719 FT_F26Dot6 y = -metrics.horiBearingY; 720 FT_F26Dot6 x2 = x + metrics.width; 721 FT_F26Dot6 y2 = y + metrics.height; 722 // Synthetic bold moves the glyph top and right boundaries. 723 y -= bold.y; 724 x2 += bold.x; 725 if (hintMetrics) { 726 if (roundX && unhintedX) { 727 x &= -64; 728 x2 = (x2 + 63) & -64; 729 } 730 if (unhintedY) { 731 y &= -64; 732 y2 = (y2 + 63) & -64; 733 } 734 } 735 *aBounds = IntRect(x, y, x2 - x, y2 - y); 736 737 // Color fonts may not have reported the right bounds here, if there wasn't 738 // an outline for the nominal glyph ID. 739 // In principle we could use COLRFonts::GetColorGlyphBounds to retrieve the 740 // true bounds of the rendering, but that's more expensive; probably better 741 // to just use the font-wide ascent/descent as a heuristic that will 742 // generally ensure everything gets rendered. 743 if (aBounds->IsEmpty() && 744 GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R'))) { 745 const auto& fm = GetMetrics(nsFontMetrics::eHorizontal); 746 // aBounds is stored as FT_F26Dot6, so scale values from `fm` by 64. 747 aBounds->y = int32_t(-NS_round(fm.maxAscent * 64.0)); 748 aBounds->height = 749 int32_t(NS_round((fm.maxAscent + fm.maxDescent) * 64.0)); 750 aBounds->x = 0; 751 aBounds->width = 752 int32_t(aAdvance ? *aAdvance : NS_round(fm.maxAdvance * 64.0)); 753 } 754 } 755 756 return true; 757 } 758 759 /** 760 * Get the cached glyph metrics for the glyph id if available. Otherwise, query 761 * FreeType for the glyph extents and initialize the glyph metrics. 762 */ 763 const gfxFT2FontBase::GlyphMetrics& gfxFT2FontBase::GetCachedGlyphMetrics( 764 uint16_t aGID, IntRect* aBounds) { 765 { 766 // Try to read cached metrics without exclusive locking. 767 AutoReadLock lock(mLock); 768 if (mGlyphMetrics) { 769 if (auto metrics = mGlyphMetrics->Lookup(aGID)) { 770 return metrics.Data(); 771 } 772 } 773 } 774 775 // We need to create/update the cache. 776 AutoWriteLock lock(mLock); 777 if (!mGlyphMetrics) { 778 mGlyphMetrics = 779 mozilla::MakeUnique<nsTHashMap<nsUint32HashKey, GlyphMetrics>>(128); 780 } 781 782 return mGlyphMetrics->LookupOrInsertWith(aGID, [&] { 783 GlyphMetrics metrics; 784 IntRect bounds; 785 if (GetFTGlyphExtents(aGID, &metrics.mAdvance, &bounds)) { 786 metrics.SetBounds(bounds); 787 if (aBounds) { 788 *aBounds = bounds; 789 } 790 } 791 return metrics; 792 }); 793 } 794 795 bool gfxFT2FontBase::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, 796 bool aTight) { 797 IntRect bounds; 798 const GlyphMetrics& metrics = GetCachedGlyphMetrics(aGID, &bounds); 799 if (!metrics.HasValidBounds()) { 800 return false; 801 } 802 // Check if there are cached bounds and use those if available. Otherwise, 803 // fall back to directly querying the glyph extents. 804 if (metrics.HasCachedBounds()) { 805 bounds = metrics.GetBounds(); 806 } else if (bounds.IsEmpty() && !GetFTGlyphExtents(aGID, nullptr, &bounds)) { 807 return false; 808 } 809 // The bounds are stored unscaled, so must be scaled to the adjusted size. 810 *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize); 811 return true; 812 } 813 814 // For variation fonts, figure out the variation coordinates to be applied 815 // for each axis, in freetype's order (which may not match the order of 816 // axes in mStyle.variationSettings, so we need to search by axis tag). 817 /*static*/ 818 void gfxFT2FontBase::SetupVarCoords( 819 FT_MM_Var* aMMVar, const nsTArray<gfxFontVariation>& aVariations, 820 FT_Face aFTFace) { 821 if (!aMMVar) { 822 return; 823 } 824 825 nsTArray<FT_Fixed> coords; 826 for (unsigned i = 0; i < aMMVar->num_axis; ++i) { 827 coords.AppendElement(aMMVar->axis[i].def); 828 for (const auto& v : aVariations) { 829 if (aMMVar->axis[i].tag == v.mTag) { 830 FT_Fixed val = v.mValue * 0x10000; 831 val = std::min(val, aMMVar->axis[i].maximum); 832 val = std::max(val, aMMVar->axis[i].minimum); 833 coords[i] = val; 834 break; 835 } 836 } 837 } 838 839 if (!coords.IsEmpty()) { 840 #if MOZ_TREE_FREETYPE 841 FT_Set_Var_Design_Coordinates(aFTFace, coords.Length(), coords.Elements()); 842 #else 843 typedef FT_Error (*SetCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*); 844 static SetCoordsFunc setCoords; 845 static bool firstTime = true; 846 if (firstTime) { 847 firstTime = false; 848 setCoords = 849 (SetCoordsFunc)dlsym(RTLD_DEFAULT, "FT_Set_Var_Design_Coordinates"); 850 } 851 if (setCoords) { 852 (*setCoords)(aFTFace, coords.Length(), coords.Elements()); 853 } 854 #endif 855 } 856 }