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