hb-coretext.cc (18341B)
1 /* 2 * Copyright © 2012,2013 Mozilla Foundation. 3 * Copyright © 2012,2013 Google, Inc. 4 * 5 * This is part of HarfBuzz, a text shaping library. 6 * 7 * Permission is hereby granted, without written agreement and without 8 * license or royalty fees, to use, copy, modify, and distribute this 9 * software and its documentation for any purpose, provided that the 10 * above copyright notice and the following two paragraphs appear in 11 * all copies of this software. 12 * 13 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR 14 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 15 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN 16 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 17 * DAMAGE. 18 * 19 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, 20 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 21 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS 22 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO 23 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 24 * 25 * Mozilla Author(s): Jonathan Kew 26 * Google Author(s): Behdad Esfahbod 27 */ 28 29 #include "hb.hh" 30 31 #ifdef HAVE_CORETEXT 32 33 #include "hb-shaper-impl.hh" 34 35 #include "hb-coretext.hh" 36 37 38 /** 39 * SECTION:hb-coretext 40 * @title: hb-coretext 41 * @short_description: CoreText integration 42 * @include: hb-coretext.h 43 * 44 * Functions for using HarfBuzz with the CoreText fonts. 45 **/ 46 47 static void 48 release_table_data (void *user_data) 49 { 50 CFDataRef cf_data = reinterpret_cast<CFDataRef> (user_data); 51 CFRelease(cf_data); 52 } 53 54 static hb_blob_t * 55 _hb_cg_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void *user_data) 56 { 57 CGFontRef cg_font = reinterpret_cast<CGFontRef> (user_data); 58 CFDataRef cf_data = CGFontCopyTableForTag (cg_font, tag); 59 if (unlikely (!cf_data)) 60 return nullptr; 61 62 const char *data = reinterpret_cast<const char*> (CFDataGetBytePtr (cf_data)); 63 const size_t length = CFDataGetLength (cf_data); 64 if (!data || !length) 65 { 66 CFRelease (cf_data); 67 return nullptr; 68 } 69 70 return hb_blob_create (data, length, HB_MEMORY_MODE_READONLY, 71 reinterpret_cast<void *> (const_cast<__CFData *> (cf_data)), 72 release_table_data); 73 } 74 75 static unsigned 76 _hb_cg_get_table_tags (const hb_face_t *face HB_UNUSED, 77 unsigned int start_offset, 78 unsigned int *table_count, 79 hb_tag_t *table_tags, 80 void *user_data) 81 { 82 CGFontRef cg_font = reinterpret_cast<CGFontRef> (user_data); 83 84 CTFontRef ct_font = create_ct_font (cg_font, (CGFloat) HB_CORETEXT_DEFAULT_FONT_SIZE); 85 86 auto arr = CTFontCopyAvailableTables (ct_font, kCTFontTableOptionNoOptions); 87 88 unsigned population = (unsigned) CFArrayGetCount (arr); 89 unsigned end_offset; 90 91 if (!table_count) 92 goto done; 93 94 if (unlikely (start_offset >= population)) 95 { 96 *table_count = 0; 97 goto done; 98 } 99 100 end_offset = start_offset + *table_count; 101 if (unlikely (end_offset < start_offset)) 102 { 103 *table_count = 0; 104 goto done; 105 } 106 end_offset= hb_min (end_offset, (unsigned) population); 107 108 *table_count = end_offset - start_offset; 109 for (unsigned i = start_offset; i < end_offset; i++) 110 { 111 CTFontTableTag tag = (CTFontTableTag)(uintptr_t) CFArrayGetValueAtIndex (arr, i); 112 table_tags[i - start_offset] = tag; 113 } 114 115 done: 116 CFRelease (arr); 117 CFRelease (ct_font); 118 return population; 119 } 120 121 static void 122 _hb_cg_font_release (void *data) 123 { 124 CGFontRelease ((CGFontRef) data); 125 } 126 127 128 static CTFontDescriptorRef 129 get_last_resort_font_desc () 130 { 131 // TODO Handle allocation failures? 132 CTFontDescriptorRef last_resort = CTFontDescriptorCreateWithNameAndSize (CFSTR("LastResort"), 0); 133 CFArrayRef cascade_list = CFArrayCreate (kCFAllocatorDefault, 134 (const void **) &last_resort, 135 1, 136 &kCFTypeArrayCallBacks); 137 CFRelease (last_resort); 138 CFDictionaryRef attributes = CFDictionaryCreate (kCFAllocatorDefault, 139 (const void **) &kCTFontCascadeListAttribute, 140 (const void **) &cascade_list, 141 1, 142 &kCFTypeDictionaryKeyCallBacks, 143 &kCFTypeDictionaryValueCallBacks); 144 CFRelease (cascade_list); 145 146 CTFontDescriptorRef font_desc = CTFontDescriptorCreateWithAttributes (attributes); 147 CFRelease (attributes); 148 return font_desc; 149 } 150 151 static void 152 release_data (void *info, const void *data, size_t size) 153 { 154 assert (hb_blob_get_length ((hb_blob_t *) info) == size && 155 hb_blob_get_data ((hb_blob_t *) info, nullptr) == data); 156 157 hb_blob_destroy ((hb_blob_t *) info); 158 } 159 160 CGFontRef 161 create_cg_font (CFArrayRef ct_font_desc_array, unsigned int named_instance_index) 162 { 163 if (named_instance_index == 0) 164 { 165 // Default instance. We don't know which one is it. Return the first one. 166 // We will set the correct variations on it later. 167 } 168 else 169 named_instance_index--; 170 auto ct_font_desc = (CFArrayGetCount (ct_font_desc_array) > (CFIndex) named_instance_index) ? 171 (CTFontDescriptorRef) CFArrayGetValueAtIndex (ct_font_desc_array, (CFIndex) named_instance_index) : nullptr; 172 if (unlikely (!ct_font_desc)) 173 { 174 CFRelease (ct_font_desc_array); 175 return nullptr; 176 } 177 auto ct_font = ct_font_desc ? CTFontCreateWithFontDescriptor (ct_font_desc, 0, nullptr) : nullptr; 178 CFRelease (ct_font_desc_array); 179 if (unlikely (!ct_font)) 180 return nullptr; 181 182 auto cg_font = ct_font ? CTFontCopyGraphicsFont (ct_font, nullptr) : nullptr; 183 CFRelease (ct_font); 184 185 return cg_font; 186 } 187 188 CGFontRef 189 create_cg_font (hb_blob_t *blob, unsigned int index) 190 { 191 hb_blob_make_immutable (blob); 192 unsigned int blob_length; 193 const char *blob_data = hb_blob_get_data (blob, &blob_length); 194 if (unlikely (!blob_length)) 195 DEBUG_MSG (CORETEXT, blob, "Empty blob"); 196 197 unsigned ttc_index = index & 0xFFFF; 198 unsigned named_instance_index = index >> 16; 199 200 if (ttc_index != 0) 201 { 202 DEBUG_MSG (CORETEXT, blob, "TTC index %u not supported", ttc_index); 203 return nullptr; // CoreText does not support TTCs 204 } 205 206 if (unlikely (named_instance_index != 0)) 207 { 208 // https://github.com/harfbuzz/harfbuzz/issues/5300 209 // https://github.com/harfbuzz/harfbuzz/issues/5354 210 #if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000) || \ 211 (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) || \ 212 (defined(__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED >= 110000) || \ 213 (defined(__WATCH_OS_VERSION_MIN_REQUIRED) && __WATCH_OS_VERSION_MIN_REQUIRED >= 40000) || \ 214 (defined(__MACCATALYST_VERSION_MIN_REQUIRED) && __MACCATALYST_VERSION_MIN_REQUIRED >= 130100) || \ 215 (defined(__VISION_OS_VERSION_MIN_REQUIRED) && __VISION_OS_VERSION_MIN_REQUIRED >= 10000) 216 auto ct_font_desc_array = CTFontManagerCreateFontDescriptorsFromData (CFDataCreate (kCFAllocatorDefault, (const UInt8 *) blob_data, blob_length)); 217 if (likely (ct_font_desc_array)) 218 return create_cg_font (ct_font_desc_array, named_instance_index); 219 #endif 220 return nullptr; 221 } 222 223 hb_blob_reference (blob); 224 CGDataProviderRef provider = CGDataProviderCreateWithData (blob, blob_data, blob_length, &release_data); 225 CGFontRef cg_font = nullptr; 226 if (likely (provider)) 227 { 228 cg_font = CGFontCreateWithDataProvider (provider); 229 if (unlikely (!cg_font)) 230 DEBUG_MSG (CORETEXT, blob, "CGFontCreateWithDataProvider() failed"); 231 CGDataProviderRelease (provider); 232 } 233 return cg_font; 234 } 235 236 CGFontRef 237 create_cg_font (hb_face_t *face) 238 { 239 CGFontRef cg_font = nullptr; 240 if (face->destroy == _hb_cg_font_release) 241 cg_font = CGFontRetain ((CGFontRef) face->user_data); 242 else 243 { 244 hb_blob_t *blob = hb_face_reference_blob (face); 245 cg_font = create_cg_font (blob, face->index); 246 hb_blob_destroy (blob); 247 } 248 return cg_font; 249 } 250 251 CTFontRef 252 create_ct_font (CGFontRef cg_font, CGFloat font_size) 253 { 254 CTFontRef ct_font = nullptr; 255 256 /* CoreText does not enable trak table usage / tracking when creating a CTFont 257 * using CTFontCreateWithGraphicsFont. The only way of enabling tracking seems 258 * to be through the CTFontCreateUIFontForLanguage call. */ 259 CFStringRef cg_postscript_name = CGFontCopyPostScriptName (cg_font); 260 if (CFStringHasPrefix (cg_postscript_name, CFSTR (".SFNSText")) || 261 CFStringHasPrefix (cg_postscript_name, CFSTR (".SFNSDisplay"))) 262 { 263 #if !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && MAC_OS_X_VERSION_MIN_REQUIRED < 1080 264 # define kCTFontUIFontSystem kCTFontSystemFontType 265 # define kCTFontUIFontEmphasizedSystem kCTFontEmphasizedSystemFontType 266 #endif 267 CTFontUIFontType font_type = kCTFontUIFontSystem; 268 if (CFStringHasSuffix (cg_postscript_name, CFSTR ("-Bold"))) 269 font_type = kCTFontUIFontEmphasizedSystem; 270 271 ct_font = CTFontCreateUIFontForLanguage (font_type, font_size, nullptr); 272 CFStringRef ct_result_name = CTFontCopyPostScriptName(ct_font); 273 if (CFStringCompare (ct_result_name, cg_postscript_name, 0) != kCFCompareEqualTo) 274 { 275 CFRelease(ct_font); 276 ct_font = nullptr; 277 } 278 CFRelease (ct_result_name); 279 } 280 CFRelease (cg_postscript_name); 281 282 if (!ct_font) 283 ct_font = CTFontCreateWithGraphicsFont (cg_font, font_size, nullptr, nullptr); 284 285 if (unlikely (!ct_font)) { 286 DEBUG_MSG (CORETEXT, cg_font, "Font CTFontCreateWithGraphicsFont() failed"); 287 return nullptr; 288 } 289 290 /* crbug.com/576941 and crbug.com/625902 and the investigation in the latter 291 * bug indicate that the cascade list reconfiguration occasionally causes 292 * crashes in CoreText on OS X 10.9, thus let's skip this step on older 293 * operating system versions. Except for the emoji font, where _not_ 294 * reconfiguring the cascade list causes CoreText crashes. For details, see 295 * crbug.com/549610 */ 296 // 0x00070000 stands for "kCTVersionNumber10_10", see CoreText.h 297 #pragma GCC diagnostic push 298 #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 299 if (&CTGetCoreTextVersion != nullptr && CTGetCoreTextVersion() < 0x00070000) { 300 #pragma GCC diagnostic pop 301 CFStringRef fontName = CTFontCopyPostScriptName (ct_font); 302 bool isEmojiFont = CFStringCompare (fontName, CFSTR("AppleColorEmoji"), 0) == kCFCompareEqualTo; 303 CFRelease (fontName); 304 if (!isEmojiFont) 305 return ct_font; 306 } 307 308 CFURLRef original_url = nullptr; 309 #if !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 310 ATSFontRef atsFont; 311 FSRef fsref; 312 OSStatus status; 313 atsFont = CTFontGetPlatformFont (ct_font, NULL); 314 status = ATSFontGetFileReference (atsFont, &fsref); 315 if (status == noErr) 316 original_url = CFURLCreateFromFSRef (NULL, &fsref); 317 #else 318 original_url = (CFURLRef) CTFontCopyAttribute (ct_font, kCTFontURLAttribute); 319 #endif 320 321 /* Create font copy with cascade list that has LastResort first; this speeds up CoreText 322 * font fallback which we don't need anyway. */ 323 { 324 CTFontDescriptorRef last_resort_font_desc = get_last_resort_font_desc (); 325 CTFontRef new_ct_font = CTFontCreateCopyWithAttributes (ct_font, 0.0, nullptr, last_resort_font_desc); 326 CFRelease (last_resort_font_desc); 327 if (new_ct_font) 328 { 329 /* The CTFontCreateCopyWithAttributes call fails to stay on the same font 330 * when reconfiguring the cascade list and may switch to a different font 331 * when there are fonts that go by the same name, since the descriptor is 332 * just name and size. 333 * 334 * Avoid reconfiguring the cascade lists if the new font is outside the 335 * system locations that we cannot access from the sandboxed renderer 336 * process in Blink. This can be detected by the new file URL location 337 * that the newly found font points to. */ 338 CFURLRef new_url = nullptr; 339 #if !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 340 atsFont = CTFontGetPlatformFont (new_ct_font, NULL); 341 status = ATSFontGetFileReference (atsFont, &fsref); 342 if (status == noErr) 343 new_url = CFURLCreateFromFSRef (NULL, &fsref); 344 #else 345 new_url = (CFURLRef) CTFontCopyAttribute (new_ct_font, kCTFontURLAttribute); 346 #endif 347 // Keep reconfigured font if URL cannot be retrieved (seems to be the case 348 // on Mac OS 10.12 Sierra), speculative fix for crbug.com/625606 349 if (!original_url || !new_url || CFEqual (original_url, new_url)) { 350 CFRelease (ct_font); 351 ct_font = new_ct_font; 352 } else { 353 CFRelease (new_ct_font); 354 DEBUG_MSG (CORETEXT, ct_font, "Discarding reconfigured CTFont, location changed."); 355 } 356 if (new_url) 357 CFRelease (new_url); 358 } 359 else 360 DEBUG_MSG (CORETEXT, ct_font, "Font copy with empty cascade list failed"); 361 } 362 363 if (original_url) 364 CFRelease (original_url); 365 return ct_font; 366 } 367 368 /** 369 * hb_coretext_face_create: 370 * @cg_font: The CGFontRef to work upon 371 * 372 * Creates an #hb_face_t face object from the specified 373 * CGFontRef. 374 * 375 * Return value: (transfer full): The new face object 376 * 377 * Since: 0.9.10 378 */ 379 hb_face_t * 380 hb_coretext_face_create (CGFontRef cg_font) 381 { 382 hb_face_t *face = hb_face_create_for_tables (_hb_cg_reference_table, CGFontRetain (cg_font), _hb_cg_font_release); 383 hb_face_set_get_table_tags_func (face, _hb_cg_get_table_tags, cg_font, nullptr); 384 return face; 385 } 386 387 /** 388 * hb_coretext_face_create_from_file_or_fail: 389 * @file_name: A font filename 390 * @index: The index of the face within the file 391 * 392 * Creates an #hb_face_t face object from the specified 393 * font file and face index. 394 * 395 * This is similar in functionality to hb_face_create_from_file_or_fail(), 396 * but uses the CoreText library for loading the font file. 397 * 398 * Return value: (transfer full): The new face object, or `NULL` if 399 * no face is found at the specified index or the file cannot be read. 400 * 401 * Since: 10.1.0 402 */ 403 hb_face_t * 404 hb_coretext_face_create_from_file_or_fail (const char *file_name, 405 unsigned int index) 406 { 407 auto url = CFURLCreateFromFileSystemRepresentation (nullptr, 408 (const UInt8 *) file_name, 409 strlen (file_name), 410 false); 411 if (unlikely (!url)) 412 return nullptr; 413 414 auto ct_font_desc_array = CTFontManagerCreateFontDescriptorsFromURL (url); 415 if (unlikely (!ct_font_desc_array)) 416 { 417 CFRelease (url); 418 return nullptr; 419 } 420 421 unsigned ttc_index = index & 0xFFFF; 422 unsigned named_instance_index = index >> 16; 423 424 if (ttc_index != 0) 425 { 426 DEBUG_MSG (CORETEXT, nullptr, "TTC index %u not supported", ttc_index); 427 return nullptr; // CoreText does not support TTCs 428 } 429 430 auto cg_font = create_cg_font (ct_font_desc_array, named_instance_index); 431 CFRelease (url); 432 433 hb_face_t *face = hb_coretext_face_create (cg_font); 434 CFRelease (cg_font); 435 if (unlikely (hb_face_is_immutable (face))) 436 return nullptr; 437 438 hb_face_set_index (face, index); 439 440 return face; 441 } 442 443 /** 444 * hb_coretext_face_create_from_blob_or_fail: 445 * @blob: A blob containing the font data 446 * @index: The index of the face within the blob 447 * 448 * Creates an #hb_face_t face object from the specified 449 * blob and face index. 450 * 451 * This is similar in functionality to hb_face_create_from_blob_or_fail(), 452 * but uses the CoreText library for loading the font data. 453 * 454 * Return value: (transfer full): The new face object, or `NULL` if 455 * no face is found at the specified index or the blob cannot be read. 456 * 457 * Since: 11.0.0 458 */ 459 hb_face_t * 460 hb_coretext_face_create_from_blob_or_fail (hb_blob_t *blob, 461 unsigned int index) 462 { 463 auto cg_font = create_cg_font (blob, index); 464 if (unlikely (!cg_font)) 465 return nullptr; 466 467 hb_face_t *face = hb_coretext_face_create (cg_font); 468 CFRelease (cg_font); 469 if (unlikely (hb_face_is_immutable (face))) 470 return nullptr; 471 472 hb_face_set_index (face, index); 473 474 return face; 475 } 476 477 /** 478 * hb_coretext_face_get_cg_font: 479 * @face: The #hb_face_t to work upon 480 * 481 * Fetches the CGFontRef associated with an #hb_face_t 482 * face object 483 * 484 * Return value: the CGFontRef found 485 * 486 * Since: 0.9.10 487 */ 488 CGFontRef 489 hb_coretext_face_get_cg_font (hb_face_t *face) 490 { 491 return (CGFontRef) (const void *) face->data.coretext; 492 } 493 494 /** 495 * hb_coretext_font_create: 496 * @ct_font: The CTFontRef to work upon 497 * 498 * Creates an #hb_font_t font object from the specified 499 * CTFontRef. 500 * 501 * The created font uses the default font functions implemented 502 * natively by HarfBuzz. If you want to use the CoreText font functions 503 * instead (rarely needed), you can do so by calling 504 * by hb_coretext_font_set_funcs(). 505 * 506 * Return value: (transfer full): The new font object 507 * 508 * Since: 1.7.2 509 **/ 510 hb_font_t * 511 hb_coretext_font_create (CTFontRef ct_font) 512 { 513 CGFontRef cg_font = CTFontCopyGraphicsFont (ct_font, nullptr); 514 hb_face_t *face = hb_coretext_face_create (cg_font); 515 CFRelease (cg_font); 516 hb_font_t *font = hb_font_create (face); 517 hb_face_destroy (face); 518 519 if (unlikely (hb_object_is_immutable (font))) 520 return font; 521 522 hb_font_set_ptem (font, CTFontGetSize (ct_font)); 523 524 /* Copy font variations */ 525 CFDictionaryRef variations = CTFontCopyVariation (ct_font); 526 if (variations) 527 { 528 hb_vector_t<hb_variation_t> vars; 529 hb_vector_t<CFTypeRef> keys; 530 hb_vector_t<CFTypeRef> values; 531 532 CFIndex count = CFDictionaryGetCount (variations); 533 if (unlikely (!vars.alloc_exact (count) || !keys.resize_exact (count) || !values.resize_exact (count))) 534 goto done; 535 536 // Fetch them one by one and collect in a vector of our own. 537 CFDictionaryGetKeysAndValues (variations, keys.arrayZ, values.arrayZ); 538 for (CFIndex i = 0; i < count; i++) 539 { 540 int tag; 541 float value; 542 CFNumberGetValue ((CFNumberRef) keys.arrayZ[i], kCFNumberIntType, &tag); 543 CFNumberGetValue ((CFNumberRef) values.arrayZ[i], kCFNumberFloatType, &value); 544 545 hb_variation_t var = {tag, value}; 546 vars.push (var); 547 } 548 hb_font_set_variations (font, vars.arrayZ, vars.length); 549 550 done: 551 CFRelease (variations); 552 } 553 554 /* Let there be dragons here... */ 555 font->data.coretext.cmpexch (nullptr, (hb_coretext_font_data_t *) CFRetain (ct_font)); 556 557 // https://github.com/harfbuzz/harfbuzz/pull/4895#issuecomment-2408471254 558 //hb_coretext_font_set_funcs (font); 559 560 return font; 561 } 562 563 /** 564 * hb_coretext_font_get_ct_font: 565 * @font: #hb_font_t to work upon 566 * 567 * Fetches the CTFontRef associated with the specified 568 * #hb_font_t font object. 569 * 570 * Return value: the CTFontRef found 571 * 572 * Since: 0.9.10 573 */ 574 CTFontRef 575 hb_coretext_font_get_ct_font (hb_font_t *font) 576 { 577 return (CTFontRef) (const void *) font->data.coretext; 578 } 579 580 581 #endif