File: platform_font_mac.mm

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (605 lines) | stat: -rw-r--r-- 23,556 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/platform_font_mac.h"

#include <cmath>
#include <set>

#include <Cocoa/Cocoa.h>
#include <CoreText/CoreText.h>

#include "base/apple/bridging.h"
#import "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/memory/scoped_policy.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "third_party/skia/include/ports/SkTypeface_mac.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_render_params.h"

namespace gfx {

using Weight = Font::Weight;

extern "C" {
bool CTFontDescriptorIsSystemUIFont(CTFontDescriptorRef);
}

namespace {

// Returns the font style for |font|. Disregards Font::UNDERLINE, since NSFont
// does not support it as a trait.
int GetFontStyleFromCTFont(CTFontRef font) {
  int font_style = Font::NORMAL;
  NSFont* ns_font = base::apple::CFToNSPtrCast(font);
  NSFontSymbolicTraits traits = ns_font.fontDescriptor.symbolicTraits;
  if (traits & NSFontItalicTrait) {
    font_style |= Font::ITALIC;
  }
  return font_style;
}

// Returns the Font::Weight for |font|.
Weight GetFontWeightFromCTFont(CTFontRef font) {
  DCHECK(font);

  // Map CoreText weights in a manner similar to ct_weight_to_fontstyle() from
  // SkFontHost_mac.cpp, but adjusted for the weights actually used by the
  // system fonts. See PlatformFontMacTest.FontWeightAPIConsistency for details.
  // macOS uses specific float values in its constants, but individual fonts can
  // and do specify arbitrary values in the -1.0 to 1.0 range. Therefore, to
  // accommodate that, and to avoid float comparison issues, use ranges.
  constexpr struct {
    // A range of CoreText weights.
    CGFloat weight_lower;
    CGFloat weight_upper;
    Weight gfx_weight;
  } weight_map[] = {
      // NSFontWeight constants:
      //   NSFontWeightUltraLight: -0.80
      //   NSFontWeightThin: -0.60
      //   NSFontWeightLight: -0.40
      //   NSFontWeightRegular: 0.0
      //   NSFontWeightMedium: 0.23
      //   NSFontWeightSemibold: 0.30
      //   NSFontWeightBold: 0.40
      //   NSFontWeightHeavy: 0.56
      //   NSFontWeightBlack: 0.62
      //
      // Actual system font weights:
      //   .AppleSystemUIFontUltraLight: -0.80
      //   .AppleSystemUIFontThin: -0.60
      //   .AppleSystemUIFontLight: -0.40
      //   .AppleSystemUIFont: 0.0
      //   .AppleSystemUIFontMedium: 0.23
      //   .AppleSystemUIFontDemi: 0.30
      //   .AppleSystemUIFontEmphasized: 0.40
      //   .AppleSystemUIFontHeavy: 0.56
      //   .AppleSystemUIFontBlack: 0.62
      {-1.0, -0.70, Weight::THIN},          // NSFontWeightUltraLight
      {-0.70, -0.45, Weight::EXTRA_LIGHT},  // NSFontWeightThin
      {-0.45, -0.10, Weight::LIGHT},        // NSFontWeightLight
      {-0.10, 0.10, Weight::NORMAL},        // NSFontWeightRegular
      {0.10, 0.27, Weight::MEDIUM},         // NSFontWeightMedium
      {0.27, 0.35, Weight::SEMIBOLD},       // NSFontWeightSemibold
      {0.35, 0.50, Weight::BOLD},           // NSFontWeightBold
      {0.50, 0.60, Weight::EXTRA_BOLD},     // NSFontWeightHeavy
      {0.60, 1.0, Weight::BLACK},           // NSFontWeightBlack
  };

  base::apple::ScopedCFTypeRef<CFDictionaryRef> traits(CTFontCopyTraits(font));
  DCHECK(traits);
  CFNumberRef cf_weight = base::apple::GetValueFromDictionary<CFNumberRef>(
      traits.get(), kCTFontWeightTrait);
  // A missing weight attribute just means 0 -> NORMAL.
  if (!cf_weight)
    return Weight::NORMAL;

  // macOS 13.0 bug: For non-system fonts with 0-valued traits,
  // `kCFBooleanFalse` is used instead of a `CFNumberRef` of 0. See
  // https://crbug.com/1372420. Filed as FB11673021, fixed in macOS 13.1. In
  // this code path, the `base::apple::GetValueFromDictionary` call above will
  // DLOG for this case and return a null `CFNumberRef`, which will cause this
  // function to return `Weight::NORMAL`, which happens to be the correct thing
  // to do for a trait with value 0.

  // The value of kCTFontWeightTrait empirically is a kCFNumberFloat64Type
  // (double) on all tested versions of macOS. However, that doesn't really
  // matter as only the first two decimal digits need to be tested. Do not check
  // for the success of CFNumberGetValue() as it returns false for any loss of
  // value and all that is needed here is two digits of accuracy.
  CGFloat weight;
  CFNumberGetValue(cf_weight, kCFNumberCGFloatType, &weight);
  for (const auto& item : weight_map) {
    if (item.weight_lower <= weight && weight <= item.weight_upper)
      return item.gfx_weight;
  }
  return Weight::INVALID;
}

// Converts a Font::Weight value to the corresponding NSFontWeight value.
NSFontWeight ToNSFontWeight(Weight weight) {
  switch (weight) {
    case Weight::THIN:
      return NSFontWeightUltraLight;
    case Weight::EXTRA_LIGHT:
      return NSFontWeightThin;
    case Weight::LIGHT:
      return NSFontWeightLight;
    case Weight::INVALID:
    case Weight::NORMAL:
      return NSFontWeightRegular;
    case Weight::MEDIUM:
      return NSFontWeightMedium;
    case Weight::SEMIBOLD:
      return NSFontWeightSemibold;
    case Weight::BOLD:
      return NSFontWeightBold;
    case Weight::EXTRA_BOLD:
      return NSFontWeightHeavy;
    case Weight::BLACK:
      return NSFontWeightBlack;
  }
}

// Chromium uses the ISO-style, 9-value ladder of font weights (THIN-BLACK). The
// new font API in macOS also uses these weights, though they are constants
// defined in terms of CGFloat with values from -1.0 to 1.0.
//
// However, the old API used by the NSFontManager uses integer values on a
// "scale of 0 to 15". These values are used in:
//
//   -[NSFontManager availableMembersOfFontFamily:]
//   -[NSFontManager convertWeight:ofFont:]
//   -[NSFontManager fontWithFamily:traits:weight:size:]
//   -[NSFontManager weightOfFont:]
//
// Apple provides a chart of how the ISO values correspond:
// https://developer.apple.com/reference/appkit/nsfontmanager/1462321-convertweight
// However, it's more complicated than that. A survey of fonts yields the
// correspondence in this function, but the outliers imply that the ISO-style
// weight is more along the lines of "weight role within the font family" vs
// this number which is more like "how weighty is this font compared to all
// other fonts".
//
// These numbers can't really be forced to line up; different fonts disagree on
// how to map them. This function mostly follows the documented chart as
// inspired by actual fonts, and should be good enough.
NSInteger ToNSFontManagerWeight(Weight weight) {
  switch (weight) {
    case Weight::THIN:
      return 2;
    case Weight::EXTRA_LIGHT:
      return 3;
    case Weight::LIGHT:
      return 4;
    case Weight::INVALID:
    case Weight::NORMAL:
      return 5;
    case Weight::MEDIUM:
      return 6;
    case Weight::SEMIBOLD:
      return 8;
    case Weight::BOLD:
      return 9;
    case Weight::EXTRA_BOLD:
      return 10;
    case Weight::BLACK:
      return 11;
  }
}

std::string GetFamilyNameFromTypeface(sk_sp<SkTypeface> typeface) {
  SkString family;
  typeface->getFamilyName(&family);
  return family.c_str();
}

// Returns an autoreleased font of a specified system font type.
CTFontRef SystemFontForConstructorOfType(
    PlatformFontMac::SystemFontType type,
    int font_size = PlatformFontMac::kDefaultFontSize) {
  NSFont* font = nil;
  switch (type) {
    case PlatformFontMac::SystemFontType::kGeneral: {
      if (font_size == PlatformFontMac::kDefaultFontSize) {
        font_size = NSFont.systemFontSize;
      }
      font = [NSFont systemFontOfSize:font_size];
      break;
    }
    case PlatformFontMac::SystemFontType::kMenu: {
      font = [NSFont menuFontOfSize:font_size];
      break;
    }
    case PlatformFontMac::SystemFontType::kToolTip: {
      font = [NSFont toolTipsFontOfSize:font_size];
      break;
    }
    default: {
      NOTREACHED();
    }
  }

  return base::apple::NSToCFPtrCast(font);
}

std::optional<PlatformFontMac::SystemFontType>
SystemFontTypeFromUndocumentedCTFontRefInternals(CTFontRef font) {
  // The macOS APIs can't reliably derive one font from another. That's why for
  // non-system fonts PlatformFontMac::DeriveFont() uses the family name of the
  // font to find look up new fonts from scratch, and why, for system fonts, it
  // uses the system font APIs to generate new system fonts.
  //
  // Skia's font handling assumes that given a font object, new fonts can be
  // derived from it. That's absolutely not true on the Mac. However, this needs
  // to be fixed, and a rewrite of how Skia handles fonts is not on the table.
  //
  // Therefore this sad hack. If Skia provides an SkTypeface, dig into the
  // undocumented bowels of CoreText and magically determine if the font is a
  // system font. This allows PlatformFontMac to correctly derive variants of
  // the provided font.
  //
  // TODO(avi, etienneb): Figure out this font stuff.
  base::apple::ScopedCFTypeRef<CTFontDescriptorRef> descriptor(
      CTFontCopyFontDescriptor(font));
  if (CTFontDescriptorIsSystemUIFont(descriptor.get())) {
    // Assume it's the standard system font. The fact that this much is known is
    // enough.
    return PlatformFontMac::SystemFontType::kGeneral;
  } else {
    return std::nullopt;
  }
}

const std::set<std::string>& SystemFontNames() {
  static const base::NoDestructor<std::set<std::string>> names([] {
    std::set<std::string> names;
    names.insert(base::SysNSStringToUTF8(
        [NSFont systemFontOfSize:NSFont.systemFontSize].familyName));
    names.insert(base::SysNSStringToUTF8([NSFont menuFontOfSize:0].familyName));
    names.insert(
        base::SysNSStringToUTF8([NSFont toolTipsFontOfSize:0].familyName));
    return names;
  }());

  return *names;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// PlatformFontMac, public:

PlatformFontMac::PlatformFontMac(SystemFontType system_font_type, int font_size)
    : PlatformFontMac(
          SystemFontForConstructorOfType(system_font_type, font_size),
          system_font_type) {}

PlatformFontMac::PlatformFontMac(CTFontRef ct_font)
    : PlatformFontMac(ct_font, std::nullopt) {
  DCHECK(ct_font);  // nil must not be passed to this constructor.
}

PlatformFontMac::PlatformFontMac(const std::string& font_name, int font_size)
    : PlatformFontMac(
          CTFontWithSpec({font_name, font_size, Font::NORMAL, Weight::NORMAL})
              .get(),
          std::nullopt,
          {font_name, font_size, Font::NORMAL, Weight::NORMAL}) {}

PlatformFontMac::PlatformFontMac(sk_sp<SkTypeface> typeface,
                                 int font_size_pixels,
                                 const std::optional<FontRenderParams>& params)
    : PlatformFontMac(SkTypeface_GetCTFontRef(typeface.get()),
                      SystemFontTypeFromUndocumentedCTFontRefInternals(
                          SkTypeface_GetCTFontRef(typeface.get())),
                      {GetFamilyNameFromTypeface(typeface), font_size_pixels,
                       (typeface->isItalic() ? Font::ITALIC : Font::NORMAL),
                       FontWeightFromInt(typeface->fontStyle().weight())}) {}

////////////////////////////////////////////////////////////////////////////////
// PlatformFontMac, PlatformFont implementation:

Font PlatformFontMac::DeriveFont(int size_delta,
                                 int style,
                                 Weight weight) const {
  // What doesn't work?
  //
  // For all fonts, -[NSFontManager convertWeight:ofFont:] will reliably
  // misbehave, skipping over particular weights of fonts, refusing to go
  // lighter than regular unless you go heavier first, and in earlier versions
  // of the system would accidentally introduce italic fonts when changing
  // weights from a non-italic instance.
  //
  // For system fonts, -[NSFontManager convertFont:to(Not)HaveTrait:], if used
  // to change weight, will sometimes switch to a compatibility system font that
  // does not have all the weights available.
  //
  // For system fonts, the most reliable call to use is +[NSFont
  // systemFontOfSize:weight:]. This uses the new-style NSFontWeight which maps
  // perfectly to the ISO weights that Chromium uses. For non-system fonts,
  // -[NSFontManager fontWithFamily:traits:weight:size:] is the only reasonable
  // way to query fonts with more granularity than bold/non-bold short of
  // walking the font family and querying their kCTFontWeightTrait values. Font
  // descriptors hold promise but querying using them often fails to find fonts
  // that match; hopefully their matching abilities will improve in future
  // versions of the macOS.

  if (system_font_type_ == SystemFontType::kGeneral) {
    NSFont* derived = [NSFont systemFontOfSize:font_spec_.size + size_delta
                                        weight:ToNSFontWeight(weight)];
    NSFontTraitMask italic_trait_mask =
        (style & Font::ITALIC) ? NSItalicFontMask : NSUnitalicFontMask;
    derived = [NSFontManager.sharedFontManager convertFont:derived
                                               toHaveTrait:italic_trait_mask];

    return Font(new PlatformFontMac(
        base::apple::NSToCFPtrCast(derived), SystemFontType::kGeneral,
        {font_spec_.name, font_spec_.size + size_delta, style, weight}));
  } else if (system_font_type_ == SystemFontType::kMenu) {
    NSFont* derived = [NSFont menuFontOfSize:font_spec_.size + size_delta];
    return Font(new PlatformFontMac(
        base::apple::NSToCFPtrCast(derived), SystemFontType::kMenu,
        {font_spec_.name, font_spec_.size + size_delta, style, weight}));
  } else if (system_font_type_ == SystemFontType::kToolTip) {
    NSFont* derived = [NSFont toolTipsFontOfSize:font_spec_.size + size_delta];
    return Font(new PlatformFontMac(
        base::apple::NSToCFPtrCast(derived), SystemFontType::kToolTip,
        {font_spec_.name, font_spec_.size + size_delta, style, weight}));
  } else {
    base::apple::ScopedCFTypeRef<CTFontRef> derived = CTFontWithSpec(
        {font_spec_.name, font_spec_.size + size_delta, style, weight});
    return Font(new PlatformFontMac(
        derived.get(), std::nullopt,
        {font_spec_.name, font_spec_.size + size_delta, style, weight}));
  }
}

int PlatformFontMac::GetHeight() {
  return height_;
}

int PlatformFontMac::GetBaseline() {
  return ascent_;
}

int PlatformFontMac::GetCapHeight() {
  return cap_height_;
}

int PlatformFontMac::GetExpectedTextWidth(int length) {
  if (!average_width_) {
    // -[NSFont boundingRectForGlyph:] seems to always return the largest
    // bounding rect that could be needed, which produces very wide expected
    // widths for strings. Instead, compute the actual width of a string
    // containing all the lowercase characters to find a reasonable guess at the
    // average.
    NSAttributedString* attr_string = [[NSAttributedString alloc]
        initWithString:@"abcdefghijklmnopqrstuvwxyz"
            attributes:@{
              NSFontAttributeName : base::apple::CFToNSPtrCast(ct_font_.get())
            }];
    average_width_ = attr_string.size.width / attr_string.length;
    DCHECK_NE(0, average_width_);
  }
  return ceil(length * average_width_);
}

int PlatformFontMac::GetStyle() const {
  return font_spec_.style;
}

Weight PlatformFontMac::GetWeight() const {
  return font_spec_.weight;
}

const std::string& PlatformFontMac::GetFontName() const {
  return font_spec_.name;
}

std::string PlatformFontMac::GetActualFontName() const {
  return base::SysNSStringToUTF8(
      base::apple::CFToNSPtrCast(ct_font_.get()).familyName);
}

int PlatformFontMac::GetFontSize() const {
  return font_spec_.size;
}

const FontRenderParams& PlatformFontMac::GetFontRenderParams() {
  return render_params_;
}

CTFontRef PlatformFontMac::GetCTFont() const {
  return ct_font_.get();
}

sk_sp<SkTypeface> PlatformFontMac::GetNativeSkTypeface() const {
  return SkMakeTypefaceFromCTFont(GetCTFont());
}

// static
Weight PlatformFontMac::GetFontWeightFromCTFontForTesting(CTFontRef font) {
  return GetFontWeightFromCTFont(font);
}

////////////////////////////////////////////////////////////////////////////////
// PlatformFontMac, private:

PlatformFontMac::PlatformFontMac(CTFontRef ct_font,
                                 std::optional<SystemFontType> system_font_type)
    : PlatformFontMac(ct_font,
                      system_font_type,
                      {base::SysNSStringToUTF8(
                           base::apple::CFToNSPtrCast(ct_font).familyName),
                       base::ClampRound(CTFontGetSize(ct_font)),
                       GetFontStyleFromCTFont(ct_font),
                       GetFontWeightFromCTFont(ct_font)}) {}

PlatformFontMac::PlatformFontMac(CTFontRef ct_font,
                                 std::optional<SystemFontType> system_font_type,
                                 FontSpec spec)
    : ct_font_(ct_font, base::scoped_policy::RETAIN),
      system_font_type_(system_font_type),
      font_spec_(spec) {
#if DCHECK_IS_ON()
  // If system_font_type.has_value(), `ct_font` is a system font that was
  // passed in from the correct constructor. If system_font_type.has_value() is
  // false, we expect that `ct_font` is *not* a system font. We enforce this
  // expectation by checking for spec.name in the list of system font names.
  // So, if system_font_type.has_value(), or `spec.name` does not appear in the
  // list of system font names, we're in good shape.
  DCHECK(system_font_type.has_value() ||
         SystemFontNames().count(spec.name) == 0)
      << "Do not pass a system font (" << spec.name << ") to PlatformFontMac; "
      << "use the SystemFontType constructor. Extend the SystemFontType enum "
      << "if necessary.";
#endif  // DCHECK_IS_ON()
  CalculateMetricsAndInitRenderParams();
}

PlatformFontMac::~PlatformFontMac() = default;

void PlatformFontMac::CalculateMetricsAndInitRenderParams() {
  NSFont* font = base::apple::CFToNSPtrCast(ct_font_.get());
  DCHECK(font);
  ascent_ = ceil(font.ascender);
  cap_height_ = ceil(font.capHeight);

  // PlatformFontMac once used -[NSLayoutManager defaultLineHeightForFont:] to
  // initialize |height_|. However, it has a silly rounding bug. Essentially, it
  // gives round(ascent) + round(descent). E.g. Helvetica Neue at size 16 gives
  // ascent=15.4634, descent=3.38208 -> 15 + 3 = 18. When the height should be
  // at least 19. According to the OpenType specification, these values should
  // simply be added, so do that. Note this uses the already-rounded |ascent_|
  // to ensure GetBaseline() + descender fits within GetHeight() during layout.
  height_ = ceil(ascent_ + std::abs(font.descender) + font.leading);

  FontRenderParamsQuery query;
  query.families.push_back(font_spec_.name);
  query.pixel_size = font_spec_.size;
  query.style = font_spec_.style;
  query.weight = font_spec_.weight;
  render_params_ = gfx::GetFontRenderParams(query, nullptr);
}

// static
base::apple::ScopedCFTypeRef<CTFontRef> PlatformFontMac::CTFontWithSpec(
    FontSpec font_spec) {
  // One might think that a font descriptor with the NSFontWeightTrait/
  // kCTFontWeightTrait trait could be used to look up a font with a specific
  // weight. That doesn't work, though. You can ask a font for its weight, but
  // you can't use weight to query for the font.
  //
  // The way that does work is to use the old-style integer weight API.

  NSFontManager* font_manager = NSFontManager.sharedFontManager;

  NSFontTraitMask traits = 0;
  if (font_spec.style & Font::ITALIC)
    traits |= NSItalicFontMask;
  // The Mac doesn't support underline as a font trait, so just drop it.
  // (Underlines must be added as an attribute on an NSAttributedString.) Do not
  // add NSBoldFontMask here; if it is added then the weight parameter below
  // will be ignored.

  NSFont* font =
      [font_manager fontWithFamily:base::SysUTF8ToNSString(font_spec.name)
                            traits:traits
                            weight:ToNSFontManagerWeight(font_spec.weight)
                              size:font_spec.size];
  if (font) {
    return base::apple::ScopedCFTypeRef<CTFontRef>(
        base::apple::NSToCFOwnershipCast(font));
  }

  // Make one fallback attempt by looking up via font name rather than font
  // family name. With this API, the available granularity of font weight is
  // bold/not-bold, but that's what's available.
  NSFontSymbolicTraits trait_bits = 0;
  if (font_spec.weight >= Weight::BOLD)
    trait_bits |= NSFontBoldTrait;
  if (font_spec.style & Font::ITALIC)
    trait_bits |= NSFontItalicTrait;

  NSDictionary* attrs = @{
    NSFontNameAttribute : base::SysUTF8ToNSString(font_spec.name),
    NSFontTraitsAttribute : @{NSFontSymbolicTrait : @(trait_bits)},
  };
  NSFontDescriptor* descriptor =
      [NSFontDescriptor fontDescriptorWithFontAttributes:attrs];

  font = [NSFont fontWithDescriptor:descriptor size:font_spec.size];
  if (font) {
    return base::apple::ScopedCFTypeRef<CTFontRef>(
        base::apple::NSToCFOwnershipCast(font));
  }

  // If that doesn't find a font, whip up a system font to stand in for the
  // specified font.
  font = [NSFont systemFontOfSize:font_spec.size
                           weight:ToNSFontWeight(font_spec.weight)];
  font = [font_manager convertFont:font toHaveTrait:traits];
  return base::apple::ScopedCFTypeRef<CTFontRef>(
      base::apple::NSToCFOwnershipCast(font));
}

////////////////////////////////////////////////////////////////////////////////
// PlatformFont, public:

// static
PlatformFont* PlatformFont::CreateDefault() {
  return new PlatformFontMac(PlatformFontMac::SystemFontType::kGeneral);
}

// static
PlatformFont* PlatformFont::CreateFromCTFont(CTFontRef ct_font) {
  return new PlatformFontMac(ct_font);
}

// static
PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
                                                  int font_size) {
  if (!SystemFontNames().count(font_name)) {
    return new PlatformFontMac(font_name, font_size);
  }

  // `font_name` is the name of a system font. Use the APIs Apple dictates to
  // construct a system font instance. At least as of macOS 15, the family
  // names for menuFontOfSize: and toolTipsFontOfSize: are the same as the
  // system font. If font_name matches the system font name, make sure we
  // construct it using SystemFontType::kGeneral.
  if (font_name ==
      base::SysNSStringToUTF8(
          [NSFont systemFontOfSize:NSFont.systemFontSize].familyName)) {
    return new PlatformFontMac(PlatformFontMac::SystemFontType::kGeneral,
                               font_size);
  }

  if (font_name ==
      base::SysNSStringToUTF8([NSFont menuFontOfSize:0].familyName)) {
    return new PlatformFontMac(PlatformFontMac::SystemFontType::kMenu,
                               font_size);
  }

  return new PlatformFontMac(PlatformFontMac::SystemFontType::kToolTip,
                             font_size);
}

// static
PlatformFont* PlatformFont::CreateFromSkTypeface(
    sk_sp<SkTypeface> typeface,
    int font_size_pixels,
    const std::optional<FontRenderParams>& params) {
  return new PlatformFontMac(typeface, font_size_pixels, params);
}

}  // namespace gfx