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