tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ScaledFontMac.cpp (29081B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ScaledFontMac.h"
      8 #include "UnscaledFontMac.h"
      9 #include "mozilla/webrender/WebRenderTypes.h"
     10 #ifdef MOZ_WIDGET_COCOA
     11 #  include "nsCocoaFeatures.h"
     12 #endif
     13 #include "PathSkia.h"
     14 #include "skia/include/core/SkFont.h"
     15 #include "skia/include/core/SkFontTypes.h"
     16 #include "skia/include/core/SkPaint.h"
     17 #include "skia/include/core/SkPath.h"
     18 #include "skia/include/ports/SkTypeface_mac.h"
     19 #include <vector>
     20 #include <dlfcn.h>
     21 #ifdef MOZ_WIDGET_UIKIT
     22 #  include <CoreFoundation/CoreFoundation.h>
     23 #endif
     24 #include "mozilla/gfx/Logging.h"
     25 
     26 #ifdef MOZ_WIDGET_COCOA
     27 // prototype for private API
     28 extern "C" {
     29 CGPathRef CGFontGetGlyphPath(CGFontRef fontRef,
     30                             CGAffineTransform* textTransform, int unknown,
     31                             CGGlyph glyph);
     32 };
     33 #endif
     34 
     35 #include "cairo-quartz.h"
     36 
     37 namespace mozilla {
     38 namespace gfx {
     39 
     40 // Simple helper class to automatically release a CFObject when it goes out
     41 // of scope.
     42 template <class T>
     43 class AutoRelease final {
     44 public:
     45  explicit AutoRelease(T aObject) : mObject(aObject) {}
     46 
     47  ~AutoRelease() {
     48    if (mObject) {
     49      CFRelease(mObject);
     50    }
     51  }
     52 
     53  AutoRelease<T>& operator=(const T& aObject) {
     54    if (aObject != mObject) {
     55      if (mObject) {
     56        CFRelease(mObject);
     57      }
     58      mObject = aObject;
     59    }
     60    return *this;
     61  }
     62 
     63  operator T() { return mObject; }
     64 
     65  T forget() {
     66    T obj = mObject;
     67    mObject = nullptr;
     68    return obj;
     69  }
     70 
     71 private:
     72  T mObject;
     73 };
     74 
     75 // Helper to create a CTFont from a CGFont, copying any variations that were
     76 // set on the CGFont, and applying attributes from (optional) aFontDesc.
     77 CTFontRef CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont, CGFloat aSize,
     78                                               bool aInstalledFont,
     79                                               CTFontDescriptorRef aFontDesc) {
     80 #ifdef MOZ_WIDGET_COCOA
     81  // New implementation (see bug 1856035) for macOS 13+.
     82  if (nsCocoaFeatures::OnVenturaOrLater()) {
     83    // Create CTFont, applying any descriptor that was passed (used by
     84    // gfxCoreTextShaper to set features).
     85    AutoRelease<CTFontRef> ctFont(
     86        CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, aFontDesc));
     87    AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont));
     88    if (vars) {
     89      // Create an attribute dictionary containing the variations.
     90      AutoRelease<CFDictionaryRef> attrs(CFDictionaryCreate(
     91          nullptr, (const void**)&kCTFontVariationAttribute,
     92          (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
     93          &kCFTypeDictionaryValueCallBacks));
     94      // Get the original descriptor from the CTFont, then add the variations
     95      // attribute to it.
     96      AutoRelease<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(ctFont));
     97      desc = CTFontDescriptorCreateCopyWithAttributes(desc, attrs);
     98      // Return a copy of the font that has the variations added.
     99      return CTFontCreateCopyWithAttributes(ctFont, 0.0, nullptr, desc);
    100    }
    101    // No variations to set, just return the default CTFont.
    102    return ctFont.forget();
    103  }
    104 #endif
    105 
    106  // Older implementation used up to macOS 12.
    107  CTFontRef ctFont;
    108  if (aInstalledFont) {
    109    AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont));
    110    if (vars) {
    111      AutoRelease<CFDictionaryRef> varAttr(CFDictionaryCreate(
    112          nullptr, (const void**)&kCTFontVariationAttribute,
    113          (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
    114          &kCFTypeDictionaryValueCallBacks));
    115 
    116      AutoRelease<CTFontDescriptorRef> varDesc(
    117          aFontDesc
    118              ? ::CTFontDescriptorCreateCopyWithAttributes(aFontDesc, varAttr)
    119              : ::CTFontDescriptorCreateWithAttributes(varAttr));
    120 
    121      ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, varDesc);
    122    } else {
    123      ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr);
    124    }
    125  } else {
    126    ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr);
    127  }
    128  return ctFont;
    129 }
    130 
    131 ScaledFontMac::ScaledFontMac(CGFontRef aFont,
    132                             const RefPtr<UnscaledFont>& aUnscaledFont,
    133                             Float aSize, bool aOwnsFont,
    134                             bool aUseFontSmoothing, bool aApplySyntheticBold,
    135                             bool aHasColorGlyphs)
    136    : ScaledFontBase(aUnscaledFont, aSize),
    137      mFont(aFont),
    138      mUseFontSmoothing(aUseFontSmoothing),
    139      mApplySyntheticBold(aApplySyntheticBold),
    140      mHasColorGlyphs(aHasColorGlyphs) {
    141  if (!aOwnsFont) {
    142    // XXX: should we be taking a reference
    143    CGFontRetain(aFont);
    144  }
    145 
    146  auto unscaledMac = static_cast<UnscaledFontMac*>(aUnscaledFont.get());
    147  bool dataFont = unscaledMac->IsDataFont();
    148  mCTFont = CreateCTFontFromCGFontWithVariations(aFont, aSize, !dataFont);
    149 }
    150 
    151 ScaledFontMac::ScaledFontMac(CTFontRef aFont,
    152                             const RefPtr<UnscaledFont>& aUnscaledFont,
    153                             bool aUseFontSmoothing, bool aApplySyntheticBold,
    154                             bool aHasColorGlyphs)
    155    : ScaledFontBase(aUnscaledFont, CTFontGetSize(aFont)),
    156      mCTFont(aFont),
    157      mUseFontSmoothing(aUseFontSmoothing),
    158      mApplySyntheticBold(aApplySyntheticBold),
    159      mHasColorGlyphs(aHasColorGlyphs) {
    160  mFont = CTFontCopyGraphicsFont(aFont, nullptr);
    161 
    162  CFRetain(mCTFont);
    163 }
    164 
    165 ScaledFontMac::~ScaledFontMac() {
    166  CFRelease(mCTFont);
    167  CGFontRelease(mFont);
    168 }
    169 
    170 SkTypeface* ScaledFontMac::CreateSkTypeface() {
    171  return SkMakeTypefaceFromCTFont(mCTFont).release();
    172 }
    173 
    174 void ScaledFontMac::SetupSkFontDrawOptions(SkFont& aFont) {
    175  aFont.setSubpixel(true);
    176 
    177  // Normally, Skia enables LCD FontSmoothing which creates thicker fonts
    178  // and also enables subpixel AA. CoreGraphics without font smoothing
    179  // explicitly creates thinner fonts and grayscale AA.
    180  // CoreGraphics doesn't support a configuration that produces thicker
    181  // fonts with grayscale AA as LCD Font Smoothing enables or disables
    182  // both. However, Skia supports it by enabling font smoothing (producing
    183  // subpixel AA) and converts it to grayscale AA. Since Skia doesn't
    184  // support subpixel AA on transparent backgrounds, we still want font
    185  // smoothing for the thicker fonts, even if it is grayscale AA.
    186  //
    187  // With explicit Grayscale AA (from -moz-osx-font-smoothing:grayscale),
    188  // we want to have grayscale AA with no smoothing at all. This means
    189  // disabling the LCD font smoothing behaviour.
    190  // To accomplish this we have to explicitly disable hinting,
    191  // and disable LCDRenderText.
    192  if (aFont.getEdging() == SkFont::Edging::kAntiAlias && !mUseFontSmoothing) {
    193    aFont.setHinting(SkFontHinting::kNone);
    194  }
    195 }
    196 
    197 // private API here are the public options on OS X
    198 // CTFontCreatePathForGlyph
    199 // ATSUGlyphGetCubicPaths
    200 // we've used this in cairo sucessfully for some time.
    201 // Note: cairo dlsyms it. We could do that but maybe it's
    202 // safe just to use?
    203 
    204 already_AddRefed<Path> ScaledFontMac::GetPathForGlyphs(
    205    const GlyphBuffer& aBuffer, const DrawTarget* aTarget) {
    206  return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget);
    207 }
    208 
    209 static uint32_t CalcTableChecksum(const uint32_t* tableStart, uint32_t length,
    210                                  bool skipChecksumAdjust = false) {
    211  uint32_t sum = 0L;
    212  const uint32_t* table = tableStart;
    213  const uint32_t* end = table + length / sizeof(uint32_t);
    214  while (table < end) {
    215    if (skipChecksumAdjust && (table - tableStart) == 2) {
    216      table++;
    217    } else {
    218      sum += CFSwapInt32BigToHost(*table++);
    219    }
    220  }
    221 
    222  // The length is not 4-byte aligned, but we still must process the remaining
    223  // bytes.
    224  if (length & 3) {
    225    // Pad with zero before adding to the checksum.
    226    uint32_t last = 0;
    227    memcpy(&last, end, length & 3);
    228    sum += CFSwapInt32BigToHost(last);
    229  }
    230 
    231  return sum;
    232 }
    233 
    234 struct TableRecord {
    235  uint32_t tag;
    236  uint32_t checkSum;
    237  uint32_t offset;
    238  uint32_t length;
    239  CFDataRef data;
    240 };
    241 
    242 static int maxPow2LessThanEqual(int a) {
    243  int x = 1;
    244  int shift = 0;
    245  while ((x << (shift + 1)) <= a) {
    246    shift++;
    247  }
    248  return shift;
    249 }
    250 
    251 struct writeBuf final {
    252  explicit writeBuf(int size) {
    253    this->data = new unsigned char[size];
    254    this->offset = 0;
    255  }
    256  ~writeBuf() { delete[] this->data; }
    257 
    258  template <class T>
    259  void writeElement(T a) {
    260    *reinterpret_cast<T*>(&this->data[this->offset]) = a;
    261    this->offset += sizeof(T);
    262  }
    263 
    264  void writeMem(const void* data, unsigned long length) {
    265    memcpy(&this->data[this->offset], data, length);
    266    this->offset += length;
    267  }
    268 
    269  void align() {
    270    while (this->offset & 3) {
    271      this->data[this->offset] = 0;
    272      this->offset++;
    273    }
    274  }
    275 
    276  unsigned char* data;
    277  int offset;
    278 };
    279 
    280 bool UnscaledFontMac::GetFontFileData(FontFileDataOutput aDataCallback,
    281                                      void* aBaton) {
    282  // We'll reconstruct a TTF font from the tables we can get from the CGFont
    283  CFArrayRef tags = CGFontCopyTableTags(mFont);
    284  CFIndex count = CFArrayGetCount(tags);
    285 
    286  TableRecord* records = new TableRecord[count];
    287  uint32_t offset = 0;
    288  offset += sizeof(uint32_t) * 3;
    289  offset += sizeof(uint32_t) * 4 * count;
    290  bool CFF = false;
    291  for (CFIndex i = 0; i < count; i++) {
    292    uint32_t tag = (uint32_t)(uintptr_t)CFArrayGetValueAtIndex(tags, i);
    293    if (tag == 0x43464620 || tag == 0x43464632) {  // 'CFF ', 'CFF2'
    294      CFF = true;
    295    }
    296    CFDataRef data = CGFontCopyTableForTag(mFont, tag);
    297    // Bug 1602391 suggests CGFontCopyTableForTag can fail, even though we just
    298    // got the tag from the font via CGFontCopyTableTags above. If we can catch
    299    // this (e.g. in fuzz-testing) it'd be good to understand when it happens,
    300    // but in any case we'll handle it safely below by treating the table as
    301    // zero-length.
    302    MOZ_ASSERT(data, "failed to get font table data");
    303    records[i].tag = tag;
    304    records[i].offset = offset;
    305    records[i].data = data;
    306    if (data) {
    307      records[i].length = CFDataGetLength(data);
    308      bool skipChecksumAdjust = (tag == 0x68656164);  // 'head'
    309      records[i].checkSum = CalcTableChecksum(
    310          reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(data)),
    311          records[i].length, skipChecksumAdjust);
    312      offset += records[i].length;
    313      // 32 bit align the tables
    314      offset = (offset + 3) & ~3;
    315    } else {
    316      records[i].length = 0;
    317      records[i].checkSum = 0;
    318    }
    319  }
    320  CFRelease(tags);
    321 
    322  struct writeBuf buf(offset);
    323  // write header/offset table
    324  if (CFF) {
    325    buf.writeElement(CFSwapInt32HostToBig(0x4f54544f));
    326  } else {
    327    buf.writeElement(CFSwapInt32HostToBig(0x00010000));
    328  }
    329  buf.writeElement(CFSwapInt16HostToBig(count));
    330  int maxPow2Count = maxPow2LessThanEqual(count);
    331  buf.writeElement(CFSwapInt16HostToBig((1 << maxPow2Count) * 16));
    332  buf.writeElement(CFSwapInt16HostToBig(maxPow2Count));
    333  buf.writeElement(CFSwapInt16HostToBig((count - (1 << maxPow2Count)) * 16));
    334 
    335  // write table record entries
    336  for (CFIndex i = 0; i < count; i++) {
    337    buf.writeElement(CFSwapInt32HostToBig(records[i].tag));
    338    buf.writeElement(CFSwapInt32HostToBig(records[i].checkSum));
    339    buf.writeElement(CFSwapInt32HostToBig(records[i].offset));
    340    buf.writeElement(CFSwapInt32HostToBig(records[i].length));
    341  }
    342 
    343  // write tables
    344  int checkSumAdjustmentOffset = 0;
    345  for (CFIndex i = 0; i < count; i++) {
    346    if (records[i].tag == 0x68656164) {
    347      checkSumAdjustmentOffset = buf.offset + 2 * 4;
    348    }
    349    if (records[i].data) {
    350      buf.writeMem(CFDataGetBytePtr(records[i].data), records[i].length);
    351      buf.align();
    352      CFRelease(records[i].data);
    353    }
    354  }
    355  delete[] records;
    356 
    357  // clear the checksumAdjust field before checksumming the whole font
    358  memset(&buf.data[checkSumAdjustmentOffset], 0, sizeof(uint32_t));
    359  uint32_t fontChecksum = CFSwapInt32HostToBig(
    360      0xb1b0afba -
    361      CalcTableChecksum(reinterpret_cast<const uint32_t*>(buf.data), offset));
    362  // set checkSumAdjust to the computed checksum
    363  memcpy(&buf.data[checkSumAdjustmentOffset], &fontChecksum,
    364         sizeof(fontChecksum));
    365 
    366  // we always use an index of 0
    367  aDataCallback(buf.data, buf.offset, 0, aBaton);
    368 
    369  return true;
    370 }
    371 
    372 bool UnscaledFontMac::GetFontDescriptor(FontDescriptorOutput aCb,
    373                                        void* aBaton) {
    374  if (mIsDataFont) {
    375    return false;
    376  }
    377 
    378  AutoRelease<CFStringRef> psname(CGFontCopyPostScriptName(mFont));
    379  if (!psname) {
    380    return false;
    381  }
    382 
    383  char buf[1024];
    384  const char* cstr = CFStringGetCStringPtr(psname, kCFStringEncodingUTF8);
    385  if (!cstr) {
    386    if (!CFStringGetCString(psname, buf, sizeof(buf), kCFStringEncodingUTF8)) {
    387      return false;
    388    }
    389    cstr = buf;
    390  }
    391 
    392  nsAutoCString descriptor(cstr);
    393  uint32_t psNameLen = descriptor.Length();
    394 
    395  AutoRelease<CTFontRef> ctFont(
    396      CTFontCreateWithGraphicsFont(mFont, 0, nullptr, nullptr));
    397  AutoRelease<CFURLRef> fontUrl(
    398      (CFURLRef)CTFontCopyAttribute(ctFont, kCTFontURLAttribute));
    399  if (fontUrl) {
    400    CFStringRef urlStr(CFURLCopyFileSystemPath(fontUrl, kCFURLPOSIXPathStyle));
    401    cstr = CFStringGetCStringPtr(urlStr, kCFStringEncodingUTF8);
    402    if (!cstr) {
    403      if (!CFStringGetCString(urlStr, buf, sizeof(buf),
    404                              kCFStringEncodingUTF8)) {
    405        return false;
    406      }
    407      cstr = buf;
    408    }
    409    descriptor.Append(cstr);
    410  }
    411 
    412  aCb(reinterpret_cast<const uint8_t*>(descriptor.get()), descriptor.Length(),
    413      psNameLen, aBaton);
    414  return true;
    415 }
    416 
    417 static void CollectVariationsFromDictionary(const void* aKey,
    418                                            const void* aValue,
    419                                            void* aContext) {
    420  auto keyPtr = static_cast<const CFTypeRef>(aKey);
    421  auto valuePtr = static_cast<const CFTypeRef>(aValue);
    422  auto outVariations = static_cast<std::vector<FontVariation>*>(aContext);
    423  if (CFGetTypeID(keyPtr) == CFNumberGetTypeID() &&
    424      CFGetTypeID(valuePtr) == CFNumberGetTypeID()) {
    425    uint64_t t;
    426    double v;
    427    if (CFNumberGetValue(static_cast<CFNumberRef>(keyPtr), kCFNumberSInt64Type,
    428                         &t) &&
    429        CFNumberGetValue(static_cast<CFNumberRef>(valuePtr),
    430                         kCFNumberDoubleType, &v)) {
    431      outVariations->push_back(FontVariation{uint32_t(t), float(v)});
    432    }
    433  }
    434 }
    435 
    436 static bool GetVariationsForCTFont(CTFontRef aCTFont,
    437                                   std::vector<FontVariation>* aOutVariations) {
    438  if (!aCTFont) {
    439    return true;
    440  }
    441  AutoRelease<CFDictionaryRef> dict(CTFontCopyVariation(aCTFont));
    442  CFIndex count = dict ? CFDictionaryGetCount(dict) : 0;
    443  if (count > 0) {
    444    aOutVariations->reserve(count);
    445    CFDictionaryApplyFunction(dict, CollectVariationsFromDictionary,
    446                              aOutVariations);
    447  }
    448  return true;
    449 }
    450 
    451 bool ScaledFontMac::GetFontInstanceData(FontInstanceDataOutput aCb,
    452                                        void* aBaton) {
    453  // Collect any variation settings that were incorporated into the CTFont.
    454  std::vector<FontVariation> variations;
    455  if (!GetVariationsForCTFont(mCTFont, &variations)) {
    456    return false;
    457  }
    458 
    459  InstanceData instance(this);
    460  aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance),
    461      variations.data(), variations.size(), aBaton);
    462  return true;
    463 }
    464 
    465 bool ScaledFontMac::GetWRFontInstanceOptions(
    466    Maybe<wr::FontInstanceOptions>* aOutOptions,
    467    Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
    468    std::vector<FontVariation>* aOutVariations) {
    469  GetVariationsForCTFont(mCTFont, aOutVariations);
    470 
    471  wr::FontInstanceOptions options = {};
    472  options.render_mode = wr::FontRenderMode::Subpixel;
    473  options.flags = wr::FontInstanceFlags::SUBPIXEL_POSITION;
    474  if (mUseFontSmoothing) {
    475    options.flags |= wr::FontInstanceFlags::FONT_SMOOTHING;
    476  }
    477  if (mApplySyntheticBold) {
    478    options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD;
    479  }
    480  if (mHasColorGlyphs) {
    481    options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
    482  }
    483  options.synthetic_italics =
    484      wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle());
    485  *aOutOptions = Some(options);
    486  return true;
    487 }
    488 
    489 ScaledFontMac::InstanceData::InstanceData(
    490    const wr::FontInstanceOptions* aOptions,
    491    const wr::FontInstancePlatformOptions* aPlatformOptions)
    492    : mUseFontSmoothing(true),
    493      mApplySyntheticBold(false),
    494      mHasColorGlyphs(false) {
    495  if (aOptions) {
    496    if (!(aOptions->flags & wr::FontInstanceFlags::FONT_SMOOTHING)) {
    497      mUseFontSmoothing = false;
    498    }
    499    if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) {
    500      mApplySyntheticBold = true;
    501    }
    502    if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) {
    503      mHasColorGlyphs = true;
    504    }
    505  }
    506 }
    507 
    508 static CFDictionaryRef CreateVariationDictionaryOrNull(
    509    CGFontRef aCGFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache,
    510    uint32_t aVariationCount, const FontVariation* aVariations) {
    511  if (!aCGAxesCache) {
    512    aCGAxesCache = CGFontCopyVariationAxes(aCGFont);
    513    if (!aCGAxesCache) {
    514      return nullptr;
    515    }
    516  }
    517  if (!aCTAxesCache) {
    518    AutoRelease<CTFontRef> ctFont(
    519        CTFontCreateWithGraphicsFont(aCGFont, 0, nullptr, nullptr));
    520    aCTAxesCache = CTFontCopyVariationAxes(ctFont);
    521    if (!aCTAxesCache) {
    522      return nullptr;
    523    }
    524  }
    525 
    526  CFIndex axisCount = CFArrayGetCount(aCTAxesCache);
    527  if (CFArrayGetCount(aCGAxesCache) != axisCount) {
    528    return nullptr;
    529  }
    530 
    531  AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(
    532      kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks,
    533      &kCFTypeDictionaryValueCallBacks));
    534 
    535  // Number of variation settings passed in the aVariations parameter.
    536  // This will typically be a very low value, so we just linear-search them.
    537  bool allDefaultValues = true;
    538 
    539  for (CFIndex i = 0; i < axisCount; ++i) {
    540    // We sanity-check the axis info found in the CTFont, and bail out
    541    // (returning null) if it doesn't have the expected types.
    542    CFTypeRef axisInfo = CFArrayGetValueAtIndex(aCTAxesCache, i);
    543    if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
    544      return nullptr;
    545    }
    546    CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo);
    547 
    548    CFTypeRef axisTag =
    549        CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
    550    if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) {
    551      return nullptr;
    552    }
    553    int64_t tagLong;
    554    if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag),
    555                          kCFNumberSInt64Type, &tagLong)) {
    556      return nullptr;
    557    }
    558 
    559    axisInfo = CFArrayGetValueAtIndex(aCGAxesCache, i);
    560    if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
    561      return nullptr;
    562    }
    563    CFTypeRef axisName = CFDictionaryGetValue(
    564        static_cast<CFDictionaryRef>(axisInfo), kCGFontVariationAxisName);
    565    if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) {
    566      return nullptr;
    567    }
    568 
    569    // Clamp axis values to the supported range.
    570    CFTypeRef min =
    571        CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey);
    572    CFTypeRef max =
    573        CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey);
    574    CFTypeRef def =
    575        CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey);
    576    if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max ||
    577        CFGetTypeID(max) != CFNumberGetTypeID() || !def ||
    578        CFGetTypeID(def) != CFNumberGetTypeID()) {
    579      return nullptr;
    580    }
    581    double minDouble;
    582    double maxDouble;
    583    double defDouble;
    584    if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType,
    585                          &minDouble) ||
    586        !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType,
    587                          &maxDouble) ||
    588        !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType,
    589                          &defDouble)) {
    590      return nullptr;
    591    }
    592 
    593    double value = defDouble;
    594    for (uint32_t j = 0; j < aVariationCount; ++j) {
    595      if (aVariations[j].mTag == tagLong) {
    596        value = std::clamp<double>(aVariations[j].mValue, minDouble, maxDouble);
    597        if (value != defDouble) {
    598          allDefaultValues = false;
    599        }
    600        break;
    601      }
    602    }
    603    AutoRelease<CFNumberRef> valueNumber(
    604        CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
    605    CFDictionaryAddValue(dict, axisName, valueNumber);
    606  }
    607 
    608  if (allDefaultValues) {
    609    // We didn't actually set any non-default values, so throw away the
    610    // variations dictionary and just use the default rendering.
    611    return nullptr;
    612  }
    613 
    614  return dict.forget();
    615 }
    616 
    617 static CFDictionaryRef CreateVariationTagDictionaryOrNull(
    618    CTFontRef aCTFont, uint32_t aVariationCount,
    619    const FontVariation* aVariations) {
    620  AutoRelease<CFArrayRef> axes(CTFontCopyVariationAxes(aCTFont));
    621  CFIndex axisCount = CFArrayGetCount(axes);
    622 
    623  AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(
    624      kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks,
    625      &kCFTypeDictionaryValueCallBacks));
    626 
    627  // Number of variation settings passed in the aVariations parameter.
    628  // This will typically be a very low value, so we just linear-search them.
    629  bool allDefaultValues = true;
    630 
    631  for (CFIndex i = 0; i < axisCount; ++i) {
    632    // We sanity-check the axis info found in the CTFont, and bail out
    633    // (returning null) if it doesn't have the expected types.
    634    CFTypeRef axisInfo = CFArrayGetValueAtIndex(axes, i);
    635    if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
    636      return nullptr;
    637    }
    638    CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo);
    639 
    640    CFTypeRef axisTag =
    641        CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
    642    if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) {
    643      return nullptr;
    644    }
    645    int64_t tagLong;
    646    if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag),
    647                          kCFNumberSInt64Type, &tagLong)) {
    648      return nullptr;
    649    }
    650 
    651    // Clamp axis values to the supported range.
    652    CFTypeRef min =
    653        CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey);
    654    CFTypeRef max =
    655        CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey);
    656    CFTypeRef def =
    657        CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey);
    658    if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max ||
    659        CFGetTypeID(max) != CFNumberGetTypeID() || !def ||
    660        CFGetTypeID(def) != CFNumberGetTypeID()) {
    661      return nullptr;
    662    }
    663    double minDouble;
    664    double maxDouble;
    665    double defDouble;
    666    if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType,
    667                          &minDouble) ||
    668        !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType,
    669                          &maxDouble) ||
    670        !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType,
    671                          &defDouble)) {
    672      return nullptr;
    673    }
    674 
    675    double value = defDouble;
    676    for (uint32_t j = 0; j < aVariationCount; ++j) {
    677      if (aVariations[j].mTag == tagLong) {
    678        value = std::clamp<double>(aVariations[j].mValue, minDouble, maxDouble);
    679        if (value != defDouble) {
    680          allDefaultValues = false;
    681        }
    682        break;
    683      }
    684    }
    685    AutoRelease<CFNumberRef> valueNumber(
    686        CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
    687    CFDictionaryAddValue(dict, axisTag, valueNumber);
    688  }
    689 
    690  if (allDefaultValues) {
    691    // We didn't actually set any non-default values, so throw away the
    692    // variations dictionary and just use the default rendering.
    693    return nullptr;
    694  }
    695 
    696  return dict.forget();
    697 }
    698 
    699 /* static */
    700 CGFontRef UnscaledFontMac::CreateCGFontWithVariations(
    701    CGFontRef aFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache,
    702    uint32_t aVariationCount, const FontVariation* aVariations) {
    703  if (!aVariationCount) {
    704    return nullptr;
    705  }
    706  MOZ_ASSERT(aVariations);
    707 
    708  AutoRelease<CFDictionaryRef> varDict(CreateVariationDictionaryOrNull(
    709      aFont, aCGAxesCache, aCTAxesCache, aVariationCount, aVariations));
    710  if (!varDict) {
    711    return nullptr;
    712  }
    713 
    714  return CGFontCreateCopyWithVariations(aFont, varDict);
    715 }
    716 
    717 already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFont(
    718    Float aGlyphSize, const uint8_t* aInstanceData,
    719    uint32_t aInstanceDataLength, const FontVariation* aVariations,
    720    uint32_t aNumVariations)
    721 
    722 {
    723  if (aInstanceDataLength < sizeof(ScaledFontMac::InstanceData)) {
    724    gfxWarning() << "Mac scaled font instance data is truncated.";
    725    return nullptr;
    726  }
    727  const ScaledFontMac::InstanceData& instanceData =
    728      *reinterpret_cast<const ScaledFontMac::InstanceData*>(aInstanceData);
    729  RefPtr<ScaledFontMac> scaledFont;
    730  if (mFontDesc) {
    731    AutoRelease<CTFontRef> font(
    732        CTFontCreateWithFontDescriptor(mFontDesc, aGlyphSize, nullptr));
    733    if (aNumVariations > 0) {
    734      AutoRelease<CFDictionaryRef> varDict(CreateVariationTagDictionaryOrNull(
    735          font, aNumVariations, aVariations));
    736      if (varDict) {
    737        CFDictionaryRef varAttr = CFDictionaryCreate(
    738            nullptr, (const void**)&kCTFontVariationAttribute,
    739            (const void**)&varDict, 1, &kCFTypeDictionaryKeyCallBacks,
    740            &kCFTypeDictionaryValueCallBacks);
    741        AutoRelease<CTFontDescriptorRef> fontDesc(
    742            CTFontDescriptorCreateCopyWithAttributes(mFontDesc, varAttr));
    743        if (!fontDesc) {
    744          return nullptr;
    745        }
    746        font = CTFontCreateWithFontDescriptor(fontDesc, aGlyphSize, nullptr);
    747      }
    748    }
    749    scaledFont = new ScaledFontMac(font, this, instanceData.mUseFontSmoothing,
    750                                   instanceData.mApplySyntheticBold,
    751                                   instanceData.mHasColorGlyphs);
    752  } else {
    753    CGFontRef fontRef = mFont;
    754    if (aNumVariations > 0) {
    755      CGFontRef varFont = CreateCGFontWithVariations(
    756          mFont, mCGAxesCache, mCTAxesCache, aNumVariations, aVariations);
    757      if (varFont) {
    758        fontRef = varFont;
    759      }
    760    }
    761 
    762    scaledFont = new ScaledFontMac(fontRef, this, aGlyphSize, fontRef != mFont,
    763                                   instanceData.mUseFontSmoothing,
    764                                   instanceData.mApplySyntheticBold,
    765                                   instanceData.mHasColorGlyphs);
    766  }
    767  return scaledFont.forget();
    768 }
    769 
    770 already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFontFromWRFont(
    771    Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
    772    const wr::FontInstancePlatformOptions* aPlatformOptions,
    773    const FontVariation* aVariations, uint32_t aNumVariations) {
    774  ScaledFontMac::InstanceData instanceData(aOptions, aPlatformOptions);
    775  return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData),
    776                          sizeof(instanceData), aVariations, aNumVariations);
    777 }
    778 
    779 cairo_font_face_t* ScaledFontMac::CreateCairoFontFace(
    780    cairo_font_options_t* aFontOptions) {
    781  MOZ_ASSERT(mFont);
    782  return cairo_quartz_font_face_create_for_cgfont(mFont);
    783 }
    784 
    785 already_AddRefed<UnscaledFont> UnscaledFontMac::CreateFromFontDescriptor(
    786    const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) {
    787  if (aDataLength == 0) {
    788    gfxWarning() << "Mac font descriptor is truncated.";
    789    return nullptr;
    790  }
    791  AutoRelease<CFStringRef> name(
    792      CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aData, aIndex,
    793                              kCFStringEncodingUTF8, false));
    794  if (!name) {
    795    return nullptr;
    796  }
    797  CGFontRef font = CGFontCreateWithFontName(name);
    798  if (!font) {
    799    return nullptr;
    800  }
    801 
    802  // If the descriptor included a font file path, apply that attribute and
    803  // refresh the font in case it changed.
    804  if (aIndex < aDataLength) {
    805    AutoRelease<CFStringRef> path(CFStringCreateWithBytes(
    806        kCFAllocatorDefault, (const UInt8*)aData + aIndex, aDataLength - aIndex,
    807        kCFStringEncodingUTF8, false));
    808    AutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(
    809        kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, false));
    810    AutoRelease<CFDictionaryRef> attrs(CFDictionaryCreate(
    811        nullptr, (const void**)&kCTFontURLAttribute, (const void**)&url, 1,
    812        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
    813    AutoRelease<CTFontRef> ctFont(
    814        CTFontCreateWithGraphicsFont(font, 0.0, nullptr, nullptr));
    815    AutoRelease<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(ctFont));
    816    AutoRelease<CTFontDescriptorRef> newDesc(
    817        CTFontDescriptorCreateCopyWithAttributes(desc, attrs));
    818    AutoRelease<CTFontRef> newFont(
    819        CTFontCreateWithFontDescriptor(newDesc, 0.0, nullptr));
    820    CFRelease(font);
    821    font = CTFontCopyGraphicsFont(newFont, nullptr);
    822  }
    823 
    824  RefPtr<UnscaledFont> unscaledFont = new UnscaledFontMac(font);
    825  CFRelease(font);
    826  return unscaledFont.forget();
    827 }
    828 
    829 }  // namespace gfx
    830 }  // namespace mozilla