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