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