no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / gfx / 2d / ScaledFontMac.cpp
blob7ac1cfc9c9623fe7bcfed9fd6ffcfa3b3c154a03
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/. */
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/SkPaint.h"
15 #include "skia/include/core/SkPath.h"
16 #include "skia/include/ports/SkTypeface_mac.h"
17 #include <vector>
18 #include <dlfcn.h>
19 #ifdef MOZ_WIDGET_UIKIT
20 # include <CoreFoundation/CoreFoundation.h>
21 #endif
22 #include "mozilla/gfx/Logging.h"
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);
31 #endif
33 #include "cairo-quartz.h"
35 namespace mozilla {
36 namespace gfx {
38 // Simple helper class to automatically release a CFObject when it goes out
39 // of scope.
40 template <class T>
41 class AutoRelease final {
42 public:
43 explicit AutoRelease(T aObject) : mObject(aObject) {}
45 ~AutoRelease() {
46 if (mObject) {
47 CFRelease(mObject);
51 AutoRelease<T>& operator=(const T& aObject) {
52 if (aObject != mObject) {
53 if (mObject) {
54 CFRelease(mObject);
56 mObject = aObject;
58 return *this;
61 operator T() { return mObject; }
63 T forget() {
64 T obj = mObject;
65 mObject = nullptr;
66 return obj;
69 private:
70 T mObject;
73 // Helper to create a CTFont from a CGFont, copying any variations that were
74 // set on the CGFont, and applying attributes from (optional) aFontDesc.
75 CTFontRef CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont, CGFloat aSize,
76 bool aInstalledFont,
77 CTFontDescriptorRef aFontDesc) {
78 #ifdef MOZ_WIDGET_COCOA
79 // New implementation (see bug 1856035) for macOS 13+.
80 if (nsCocoaFeatures::OnVenturaOrLater()) {
81 // Create CTFont, applying any descriptor that was passed (used by
82 // gfxCoreTextShaper to set features).
83 AutoRelease<CTFontRef> ctFont(
84 CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, aFontDesc));
85 AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont));
86 if (vars) {
87 // Create an attribute dictionary containing the variations.
88 AutoRelease<CFDictionaryRef> attrs(CFDictionaryCreate(
89 nullptr, (const void**)&kCTFontVariationAttribute,
90 (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
91 &kCFTypeDictionaryValueCallBacks));
92 // Get the original descriptor from the CTFont, then add the variations
93 // attribute to it.
94 AutoRelease<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(ctFont));
95 desc = CTFontDescriptorCreateCopyWithAttributes(desc, attrs);
96 // Return a copy of the font that has the variations added.
97 return CTFontCreateCopyWithAttributes(ctFont, 0.0, nullptr, desc);
99 // No variations to set, just return the default CTFont.
100 return ctFont.forget();
102 #endif
104 // Older implementation used up to macOS 12.
105 CTFontRef ctFont;
106 if (aInstalledFont) {
107 AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont));
108 if (vars) {
109 AutoRelease<CFDictionaryRef> varAttr(CFDictionaryCreate(
110 nullptr, (const void**)&kCTFontVariationAttribute,
111 (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
112 &kCFTypeDictionaryValueCallBacks));
114 AutoRelease<CTFontDescriptorRef> varDesc(
115 aFontDesc
116 ? ::CTFontDescriptorCreateCopyWithAttributes(aFontDesc, varAttr)
117 : ::CTFontDescriptorCreateWithAttributes(varAttr));
119 ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, varDesc);
120 } else {
121 ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr);
123 } else {
124 ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr);
126 return ctFont;
129 ScaledFontMac::ScaledFontMac(CGFontRef aFont,
130 const RefPtr<UnscaledFont>& aUnscaledFont,
131 Float aSize, bool aOwnsFont,
132 bool aUseFontSmoothing, bool aApplySyntheticBold,
133 bool aHasColorGlyphs)
134 : ScaledFontBase(aUnscaledFont, aSize),
135 mFont(aFont),
136 mUseFontSmoothing(aUseFontSmoothing),
137 mApplySyntheticBold(aApplySyntheticBold),
138 mHasColorGlyphs(aHasColorGlyphs) {
139 if (!aOwnsFont) {
140 // XXX: should we be taking a reference
141 CGFontRetain(aFont);
144 auto unscaledMac = static_cast<UnscaledFontMac*>(aUnscaledFont.get());
145 bool dataFont = unscaledMac->IsDataFont();
146 mCTFont = CreateCTFontFromCGFontWithVariations(aFont, aSize, !dataFont);
149 ScaledFontMac::ScaledFontMac(CTFontRef aFont,
150 const RefPtr<UnscaledFont>& aUnscaledFont,
151 bool aUseFontSmoothing, bool aApplySyntheticBold,
152 bool aHasColorGlyphs)
153 : ScaledFontBase(aUnscaledFont, CTFontGetSize(aFont)),
154 mCTFont(aFont),
155 mUseFontSmoothing(aUseFontSmoothing),
156 mApplySyntheticBold(aApplySyntheticBold),
157 mHasColorGlyphs(aHasColorGlyphs) {
158 mFont = CTFontCopyGraphicsFont(aFont, nullptr);
160 CFRetain(mCTFont);
163 ScaledFontMac::~ScaledFontMac() {
164 CFRelease(mCTFont);
165 CGFontRelease(mFont);
168 SkTypeface* ScaledFontMac::CreateSkTypeface() {
169 return SkMakeTypefaceFromCTFont(mCTFont).release();
172 void ScaledFontMac::SetupSkFontDrawOptions(SkFont& aFont) {
173 aFont.setSubpixel(true);
175 // Normally, Skia enables LCD FontSmoothing which creates thicker fonts
176 // and also enables subpixel AA. CoreGraphics without font smoothing
177 // explicitly creates thinner fonts and grayscale AA.
178 // CoreGraphics doesn't support a configuration that produces thicker
179 // fonts with grayscale AA as LCD Font Smoothing enables or disables
180 // both. However, Skia supports it by enabling font smoothing (producing
181 // subpixel AA) and converts it to grayscale AA. Since Skia doesn't
182 // support subpixel AA on transparent backgrounds, we still want font
183 // smoothing for the thicker fonts, even if it is grayscale AA.
185 // With explicit Grayscale AA (from -moz-osx-font-smoothing:grayscale),
186 // we want to have grayscale AA with no smoothing at all. This means
187 // disabling the LCD font smoothing behaviour.
188 // To accomplish this we have to explicitly disable hinting,
189 // and disable LCDRenderText.
190 if (aFont.getEdging() == SkFont::Edging::kAntiAlias && !mUseFontSmoothing) {
191 aFont.setHinting(SkFontHinting::kNone);
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?
202 already_AddRefed<Path> ScaledFontMac::GetPathForGlyphs(
203 const GlyphBuffer& aBuffer, const DrawTarget* aTarget) {
204 return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget);
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++);
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);
229 return sum;
232 struct TableRecord {
233 uint32_t tag;
234 uint32_t checkSum;
235 uint32_t offset;
236 uint32_t length;
237 CFDataRef data;
240 static int maxPow2LessThanEqual(int a) {
241 int x = 1;
242 int shift = 0;
243 while ((x << (shift + 1)) <= a) {
244 shift++;
246 return shift;
249 struct writeBuf final {
250 explicit writeBuf(int size) {
251 this->data = new unsigned char[size];
252 this->offset = 0;
254 ~writeBuf() { delete[] this->data; }
256 template <class T>
257 void writeElement(T a) {
258 *reinterpret_cast<T*>(&this->data[this->offset]) = a;
259 this->offset += sizeof(T);
262 void writeMem(const void* data, unsigned long length) {
263 memcpy(&this->data[this->offset], data, length);
264 this->offset += length;
267 void align() {
268 while (this->offset & 3) {
269 this->data[this->offset] = 0;
270 this->offset++;
274 unsigned char* data;
275 int offset;
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);
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;
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;
318 CFRelease(tags);
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));
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));
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));
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;
347 if (records[i].data) {
348 buf.writeMem(CFDataGetBytePtr(records[i].data), records[i].length);
349 buf.align();
350 CFRelease(records[i].data);
353 delete[] records;
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));
364 // we always use an index of 0
365 aDataCallback(buf.data, buf.offset, 0, aBaton);
367 return true;
370 bool UnscaledFontMac::GetFontDescriptor(FontDescriptorOutput aCb,
371 void* aBaton) {
372 if (mIsDataFont) {
373 return false;
376 AutoRelease<CFStringRef> psname(CGFontCopyPostScriptName(mFont));
377 if (!psname) {
378 return false;
381 char buf[1024];
382 const char* cstr = CFStringGetCStringPtr(psname, kCFStringEncodingUTF8);
383 if (!cstr) {
384 if (!CFStringGetCString(psname, buf, sizeof(buf), kCFStringEncodingUTF8)) {
385 return false;
387 cstr = buf;
390 nsAutoCString descriptor(cstr);
391 uint32_t psNameLen = descriptor.Length();
393 AutoRelease<CTFontRef> ctFont(
394 CTFontCreateWithGraphicsFont(mFont, 0, nullptr, nullptr));
395 AutoRelease<CFURLRef> fontUrl(
396 (CFURLRef)CTFontCopyAttribute(ctFont, kCTFontURLAttribute));
397 if (fontUrl) {
398 CFStringRef urlStr(CFURLCopyFileSystemPath(fontUrl, kCFURLPOSIXPathStyle));
399 cstr = CFStringGetCStringPtr(urlStr, kCFStringEncodingUTF8);
400 if (!cstr) {
401 if (!CFStringGetCString(urlStr, buf, sizeof(buf),
402 kCFStringEncodingUTF8)) {
403 return false;
405 cstr = buf;
407 descriptor.Append(cstr);
410 aCb(reinterpret_cast<const uint8_t*>(descriptor.get()), descriptor.Length(),
411 psNameLen, aBaton);
412 return true;
415 static void CollectVariationsFromDictionary(const void* aKey,
416 const void* aValue,
417 void* aContext) {
418 auto keyPtr = static_cast<const CFTypeRef>(aKey);
419 auto valuePtr = static_cast<const CFTypeRef>(aValue);
420 auto outVariations = static_cast<std::vector<FontVariation>*>(aContext);
421 if (CFGetTypeID(keyPtr) == CFNumberGetTypeID() &&
422 CFGetTypeID(valuePtr) == CFNumberGetTypeID()) {
423 uint64_t t;
424 double v;
425 if (CFNumberGetValue(static_cast<CFNumberRef>(keyPtr), kCFNumberSInt64Type,
426 &t) &&
427 CFNumberGetValue(static_cast<CFNumberRef>(valuePtr),
428 kCFNumberDoubleType, &v)) {
429 outVariations->push_back(FontVariation{uint32_t(t), float(v)});
434 static bool GetVariationsForCTFont(CTFontRef aCTFont,
435 std::vector<FontVariation>* aOutVariations) {
436 if (!aCTFont) {
437 return true;
439 AutoRelease<CFDictionaryRef> dict(CTFontCopyVariation(aCTFont));
440 CFIndex count = dict ? CFDictionaryGetCount(dict) : 0;
441 if (count > 0) {
442 aOutVariations->reserve(count);
443 CFDictionaryApplyFunction(dict, CollectVariationsFromDictionary,
444 aOutVariations);
446 return true;
449 bool ScaledFontMac::GetFontInstanceData(FontInstanceDataOutput aCb,
450 void* aBaton) {
451 // Collect any variation settings that were incorporated into the CTFont.
452 std::vector<FontVariation> variations;
453 if (!GetVariationsForCTFont(mCTFont, &variations)) {
454 return false;
457 InstanceData instance(this);
458 aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance),
459 variations.data(), variations.size(), aBaton);
460 return true;
463 bool ScaledFontMac::GetWRFontInstanceOptions(
464 Maybe<wr::FontInstanceOptions>* aOutOptions,
465 Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
466 std::vector<FontVariation>* aOutVariations) {
467 GetVariationsForCTFont(mCTFont, aOutVariations);
469 wr::FontInstanceOptions options;
470 options.render_mode = wr::FontRenderMode::Subpixel;
471 options.flags = wr::FontInstanceFlags::SUBPIXEL_POSITION;
472 if (mUseFontSmoothing) {
473 options.flags |= wr::FontInstanceFlags::FONT_SMOOTHING;
475 if (mApplySyntheticBold) {
476 options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD;
478 if (mHasColorGlyphs) {
479 options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
481 options.synthetic_italics =
482 wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle());
483 *aOutOptions = Some(options);
484 return true;
487 ScaledFontMac::InstanceData::InstanceData(
488 const wr::FontInstanceOptions* aOptions,
489 const wr::FontInstancePlatformOptions* aPlatformOptions)
490 : mUseFontSmoothing(true),
491 mApplySyntheticBold(false),
492 mHasColorGlyphs(false) {
493 if (aOptions) {
494 if (!(aOptions->flags & wr::FontInstanceFlags::FONT_SMOOTHING)) {
495 mUseFontSmoothing = false;
497 if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) {
498 mApplySyntheticBold = true;
500 if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) {
501 mHasColorGlyphs = true;
506 static CFDictionaryRef CreateVariationDictionaryOrNull(
507 CGFontRef aCGFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache,
508 uint32_t aVariationCount, const FontVariation* aVariations) {
509 if (!aCGAxesCache) {
510 aCGAxesCache = CGFontCopyVariationAxes(aCGFont);
511 if (!aCGAxesCache) {
512 return nullptr;
515 if (!aCTAxesCache) {
516 AutoRelease<CTFontRef> ctFont(
517 CTFontCreateWithGraphicsFont(aCGFont, 0, nullptr, nullptr));
518 aCTAxesCache = CTFontCopyVariationAxes(ctFont);
519 if (!aCTAxesCache) {
520 return nullptr;
524 CFIndex axisCount = CFArrayGetCount(aCTAxesCache);
525 if (CFArrayGetCount(aCGAxesCache) != axisCount) {
526 return nullptr;
529 AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(
530 kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks,
531 &kCFTypeDictionaryValueCallBacks));
533 // Number of variation settings passed in the aVariations parameter.
534 // This will typically be a very low value, so we just linear-search them.
535 bool allDefaultValues = true;
537 for (CFIndex i = 0; i < axisCount; ++i) {
538 // We sanity-check the axis info found in the CTFont, and bail out
539 // (returning null) if it doesn't have the expected types.
540 CFTypeRef axisInfo = CFArrayGetValueAtIndex(aCTAxesCache, i);
541 if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
542 return nullptr;
544 CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo);
546 CFTypeRef axisTag =
547 CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
548 if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) {
549 return nullptr;
551 int64_t tagLong;
552 if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag),
553 kCFNumberSInt64Type, &tagLong)) {
554 return nullptr;
557 axisInfo = CFArrayGetValueAtIndex(aCGAxesCache, i);
558 if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
559 return nullptr;
561 CFTypeRef axisName = CFDictionaryGetValue(
562 static_cast<CFDictionaryRef>(axisInfo), kCGFontVariationAxisName);
563 if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) {
564 return nullptr;
567 // Clamp axis values to the supported range.
568 CFTypeRef min =
569 CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey);
570 CFTypeRef max =
571 CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey);
572 CFTypeRef def =
573 CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey);
574 if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max ||
575 CFGetTypeID(max) != CFNumberGetTypeID() || !def ||
576 CFGetTypeID(def) != CFNumberGetTypeID()) {
577 return nullptr;
579 double minDouble;
580 double maxDouble;
581 double defDouble;
582 if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType,
583 &minDouble) ||
584 !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType,
585 &maxDouble) ||
586 !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType,
587 &defDouble)) {
588 return nullptr;
591 double value = defDouble;
592 for (uint32_t j = 0; j < aVariationCount; ++j) {
593 if (aVariations[j].mTag == tagLong) {
594 value = std::min(std::max<double>(aVariations[j].mValue, minDouble),
595 maxDouble);
596 if (value != defDouble) {
597 allDefaultValues = false;
599 break;
602 AutoRelease<CFNumberRef> valueNumber(
603 CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
604 CFDictionaryAddValue(dict, axisName, valueNumber);
607 if (allDefaultValues) {
608 // We didn't actually set any non-default values, so throw away the
609 // variations dictionary and just use the default rendering.
610 return nullptr;
613 return dict.forget();
616 static CFDictionaryRef CreateVariationTagDictionaryOrNull(
617 CTFontRef aCTFont, uint32_t aVariationCount,
618 const FontVariation* aVariations) {
619 AutoRelease<CFArrayRef> axes(CTFontCopyVariationAxes(aCTFont));
620 CFIndex axisCount = CFArrayGetCount(axes);
622 AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(
623 kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks,
624 &kCFTypeDictionaryValueCallBacks));
626 // Number of variation settings passed in the aVariations parameter.
627 // This will typically be a very low value, so we just linear-search them.
628 bool allDefaultValues = true;
630 for (CFIndex i = 0; i < axisCount; ++i) {
631 // We sanity-check the axis info found in the CTFont, and bail out
632 // (returning null) if it doesn't have the expected types.
633 CFTypeRef axisInfo = CFArrayGetValueAtIndex(axes, i);
634 if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
635 return nullptr;
637 CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo);
639 CFTypeRef axisTag =
640 CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
641 if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) {
642 return nullptr;
644 int64_t tagLong;
645 if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag),
646 kCFNumberSInt64Type, &tagLong)) {
647 return nullptr;
650 // Clamp axis values to the supported range.
651 CFTypeRef min =
652 CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey);
653 CFTypeRef max =
654 CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey);
655 CFTypeRef def =
656 CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey);
657 if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max ||
658 CFGetTypeID(max) != CFNumberGetTypeID() || !def ||
659 CFGetTypeID(def) != CFNumberGetTypeID()) {
660 return nullptr;
662 double minDouble;
663 double maxDouble;
664 double defDouble;
665 if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType,
666 &minDouble) ||
667 !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType,
668 &maxDouble) ||
669 !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType,
670 &defDouble)) {
671 return nullptr;
674 double value = defDouble;
675 for (uint32_t j = 0; j < aVariationCount; ++j) {
676 if (aVariations[j].mTag == tagLong) {
677 value = std::min(std::max<double>(aVariations[j].mValue, minDouble),
678 maxDouble);
679 if (value != defDouble) {
680 allDefaultValues = false;
682 break;
685 AutoRelease<CFNumberRef> valueNumber(
686 CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
687 CFDictionaryAddValue(dict, axisTag, valueNumber);
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;
696 return dict.forget();
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;
706 MOZ_ASSERT(aVariations);
708 AutoRelease<CFDictionaryRef> varDict(CreateVariationDictionaryOrNull(
709 aFont, aCGAxesCache, aCTAxesCache, aVariationCount, aVariations));
710 if (!varDict) {
711 return nullptr;
714 return CGFontCreateCopyWithVariations(aFont, varDict);
717 already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFont(
718 Float aGlyphSize, const uint8_t* aInstanceData,
719 uint32_t aInstanceDataLength, const FontVariation* aVariations,
720 uint32_t aNumVariations)
723 if (aInstanceDataLength < sizeof(ScaledFontMac::InstanceData)) {
724 gfxWarning() << "Mac scaled font instance data is truncated.";
725 return nullptr;
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;
746 font = CTFontCreateWithFontDescriptor(fontDesc, aGlyphSize, nullptr);
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;
762 scaledFont = new ScaledFontMac(fontRef, this, aGlyphSize, fontRef != mFont,
763 instanceData.mUseFontSmoothing,
764 instanceData.mApplySyntheticBold,
765 instanceData.mHasColorGlyphs);
767 return scaledFont.forget();
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);
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);
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;
791 AutoRelease<CFStringRef> name(
792 CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aData, aIndex,
793 kCFStringEncodingUTF8, false));
794 if (!name) {
795 return nullptr;
797 CGFontRef font = CGFontCreateWithFontName(name);
798 if (!font) {
799 return nullptr;
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);
824 RefPtr<UnscaledFont> unscaledFont = new UnscaledFontMac(font);
825 CFRelease(font);
826 return unscaledFont.forget();
829 } // namespace gfx
830 } // namespace mozilla