blob: 3adbcbb6a45813c259ef3502cad1a29fca2901bd [file] [edit]
/*
* Copyright © 2012,2013 Mozilla Foundation.
* Copyright © 2012,2013 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Mozilla Author(s): Jonathan Kew
* Google Author(s): Behdad Esfahbod
*/
#include "hb.hh"
#ifdef HAVE_CORETEXT
#include "hb-shaper-impl.hh"
#include "hb-coretext.hh"
/**
* SECTION:hb-coretext
* @title: hb-coretext
* @short_description: CoreText integration
* @include: hb-coretext.h
*
* Functions for using HarfBuzz with the CoreText fonts.
**/
static void
release_table_data (void *user_data)
{
CFDataRef cf_data = reinterpret_cast<CFDataRef> (user_data);
CFRelease(cf_data);
}
static hb_blob_t *
_hb_cg_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void *user_data)
{
CGFontRef cg_font = reinterpret_cast<CGFontRef> (user_data);
if (unlikely (!cg_font))
return nullptr;
hb_cf_ptr_t<CFDataRef> cf_data (CGFontCopyTableForTag (cg_font, tag));
if (unlikely (!cf_data))
return nullptr;
const char *data = reinterpret_cast<const char*> (CFDataGetBytePtr (cf_data));
const size_t length = CFDataGetLength (cf_data);
if (!data || !length)
return nullptr;
return hb_blob_create (data, length, HB_MEMORY_MODE_READONLY,
reinterpret_cast<void *> (const_cast<__CFData *> (cf_data.release ())),
release_table_data);
}
static unsigned
_hb_cg_get_table_tags (const hb_face_t *face HB_UNUSED,
unsigned int start_offset,
unsigned int *table_count,
hb_tag_t *table_tags,
void *user_data)
{
CGFontRef cg_font = reinterpret_cast<CGFontRef> (user_data);
if (unlikely (!cg_font))
{
if (table_count)
*table_count = 0;
return 0;
}
hb_cf_ptr_t<CTFontRef> ct_font (create_ct_font (cg_font, (CGFloat) HB_CORETEXT_DEFAULT_FONT_SIZE));
if (unlikely (!ct_font))
{
if (table_count)
*table_count = 0;
return 0;
}
hb_cf_ptr_t<CFArrayRef> arr (CTFontCopyAvailableTables (ct_font, kCTFontTableOptionNoOptions));
if (unlikely (!arr))
{
if (table_count)
*table_count = 0;
return 0;
}
unsigned population = (unsigned) CFArrayGetCount (arr);
if (!table_count)
return population;
if (unlikely (start_offset >= population))
{
*table_count = 0;
return population;
}
unsigned end_offset = start_offset + *table_count;
if (unlikely (end_offset < start_offset))
{
*table_count = 0;
return population;
}
end_offset = hb_min (end_offset, (unsigned) population);
*table_count = end_offset - start_offset;
if (table_tags)
for (unsigned i = start_offset; i < end_offset; i++)
{
CTFontTableTag tag = (CTFontTableTag)(uintptr_t) CFArrayGetValueAtIndex (arr, i);
table_tags[i - start_offset] = tag;
}
return population;
}
static void
_hb_cg_font_release (void *data)
{
if (data)
CGFontRelease ((CGFontRef) data);
}
static CTFontDescriptorRef
get_last_resort_font_desc ()
{
hb_cf_ptr_t<CTFontDescriptorRef> last_resort (CTFontDescriptorCreateWithNameAndSize (CFSTR("LastResort"), 0));
if (unlikely (!last_resort))
return nullptr;
const void *cascade_elements[] = { last_resort.get () };
hb_cf_ptr_t<CFArrayRef> cascade_list (CFArrayCreate (kCFAllocatorDefault,
cascade_elements,
1,
&kCFTypeArrayCallBacks));
if (unlikely (!cascade_list))
return nullptr;
const void *keys[] = { kCTFontCascadeListAttribute };
const void *values[] = { cascade_list.get () };
hb_cf_ptr_t<CFDictionaryRef> attributes (CFDictionaryCreate (kCFAllocatorDefault,
keys,
values,
1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
if (unlikely (!attributes))
return nullptr;
return CTFontDescriptorCreateWithAttributes (attributes);
}
static void
release_data (void *info, const void *data, size_t size)
{
assert (hb_blob_get_length ((hb_blob_t *) info) == size &&
hb_blob_get_data ((hb_blob_t *) info, nullptr) == data);
hb_blob_destroy ((hb_blob_t *) info);
}
CGFontRef
create_cg_font (CFArrayRef ct_font_desc_array, unsigned int named_instance_index)
{
hb_cf_ptr_t<CFArrayRef> desc_array (ct_font_desc_array);
if (unlikely (!desc_array))
return nullptr;
if (named_instance_index == 0)
{
// Default instance. We don't know which one is it. Return the first one.
// We will set the correct variations on it later.
}
else
named_instance_index--;
auto ct_font_desc = (CFArrayGetCount (desc_array) > (CFIndex) named_instance_index) ?
(CTFontDescriptorRef) CFArrayGetValueAtIndex (desc_array, (CFIndex) named_instance_index) : nullptr;
if (unlikely (!ct_font_desc))
return nullptr;
hb_cf_ptr_t<CTFontRef> ct_font (CTFontCreateWithFontDescriptor (ct_font_desc, 0, nullptr));
if (unlikely (!ct_font))
return nullptr;
return CTFontCopyGraphicsFont (ct_font, nullptr);
}
CGFontRef
create_cg_font (hb_blob_t *blob, unsigned int index)
{
hb_blob_make_immutable (blob);
unsigned int blob_length;
const char *blob_data = hb_blob_get_data (blob, &blob_length);
if (unlikely (!blob_length))
DEBUG_MSG (CORETEXT, blob, "Empty blob");
unsigned ttc_index = index & 0xFFFF;
unsigned named_instance_index = index >> 16;
if (ttc_index != 0)
{
DEBUG_MSG (CORETEXT, blob, "TTC index %u not supported", ttc_index);
return nullptr; // CoreText does not support TTCs
}
if (unlikely (named_instance_index != 0))
{
// https://github.com/harfbuzz/harfbuzz/issues/5300
// https://github.com/harfbuzz/harfbuzz/issues/5354
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000) || \
(defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) || \
(defined(__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED >= 110000) || \
(defined(__WATCH_OS_VERSION_MIN_REQUIRED) && __WATCH_OS_VERSION_MIN_REQUIRED >= 40000) || \
(defined(__MACCATALYST_VERSION_MIN_REQUIRED) && __MACCATALYST_VERSION_MIN_REQUIRED >= 130100) || \
(defined(__VISION_OS_VERSION_MIN_REQUIRED) && __VISION_OS_VERSION_MIN_REQUIRED >= 10000)
hb_cf_ptr_t<CFDataRef> data (CFDataCreate (kCFAllocatorDefault, (const UInt8 *) blob_data, blob_length));
if (data)
{
auto ct_font_desc_array = CTFontManagerCreateFontDescriptorsFromData (data);
if (likely (ct_font_desc_array))
return create_cg_font (ct_font_desc_array, named_instance_index);
}
#endif
return nullptr;
}
hb_blob_reference (blob);
hb_cf_ptr_t<CGDataProviderRef> provider (CGDataProviderCreateWithData (blob, blob_data, blob_length, &release_data));
if (unlikely (!provider))
{
hb_blob_destroy (blob);
return nullptr;
}
CGFontRef cg_font = CGFontCreateWithDataProvider (provider);
if (unlikely (!cg_font))
DEBUG_MSG (CORETEXT, blob, "CGFontCreateWithDataProvider() failed");
return cg_font;
}
CGFontRef
create_cg_font (hb_face_t *face)
{
CGFontRef cg_font = nullptr;
if (unlikely (!face))
return nullptr;
if (face->destroy == _hb_cg_font_release)
cg_font = face->user_data ? CGFontRetain ((CGFontRef) face->user_data) : nullptr;
else
{
hb_blob_t *blob = hb_face_reference_blob (face);
cg_font = create_cg_font (blob, face->index);
hb_blob_destroy (blob);
}
return cg_font;
}
CTFontRef
create_ct_font (CGFontRef cg_font, CGFloat font_size)
{
if (unlikely (!cg_font))
return nullptr;
hb_cf_ptr_t<CTFontRef> ct_font;
/* CoreText does not enable trak table usage / tracking when creating a CTFont
* using CTFontCreateWithGraphicsFont. The only way of enabling tracking seems
* to be through the CTFontCreateUIFontForLanguage call. */
hb_cf_ptr_t<CFStringRef> cg_postscript_name (CGFontCopyPostScriptName (cg_font));
if (cg_postscript_name)
{
if (CFStringHasPrefix (cg_postscript_name, CFSTR (".SFNSText")) ||
CFStringHasPrefix (cg_postscript_name, CFSTR (".SFNSDisplay")))
{
#if !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && MAC_OS_X_VERSION_MIN_REQUIRED < 1080
# define kCTFontUIFontSystem kCTFontSystemFontType
# define kCTFontUIFontEmphasizedSystem kCTFontEmphasizedSystemFontType
#endif
CTFontUIFontType font_type = kCTFontUIFontSystem;
if (CFStringHasSuffix (cg_postscript_name, CFSTR ("-Bold")))
font_type = kCTFontUIFontEmphasizedSystem;
ct_font.reset (CTFontCreateUIFontForLanguage (font_type, font_size, nullptr));
if (ct_font)
{
hb_cf_ptr_t<CFStringRef> ct_result_name (CTFontCopyPostScriptName (ct_font));
if (!ct_result_name || CFStringCompare (ct_result_name, cg_postscript_name, 0) != kCFCompareEqualTo)
ct_font.reset ();
}
}
}
if (!ct_font)
ct_font.reset (CTFontCreateWithGraphicsFont (cg_font, font_size, nullptr, nullptr));
if (unlikely (!ct_font)) {
DEBUG_MSG (CORETEXT, cg_font, "Font CTFontCreateWithGraphicsFont() failed");
return nullptr;
}
/* crbug.com/576941 and crbug.com/625902 and the investigation in the latter
* bug indicate that the cascade list reconfiguration occasionally causes
* crashes in CoreText on OS X 10.9, thus let's skip this step on older
* operating system versions. Except for the emoji font, where _not_
* reconfiguring the cascade list causes CoreText crashes. For details, see
* crbug.com/549610 */
// 0x00070000 stands for "kCTVersionNumber10_10", see CoreText.h
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
if (&CTGetCoreTextVersion != nullptr && CTGetCoreTextVersion() < 0x00070000) {
#pragma GCC diagnostic pop
hb_cf_ptr_t<CFStringRef> fontName (CTFontCopyPostScriptName (ct_font));
bool isEmojiFont = fontName && CFStringCompare (fontName, CFSTR("AppleColorEmoji"), 0) == kCFCompareEqualTo;
if (!isEmojiFont)
return ct_font.release ();
}
hb_cf_ptr_t<CFURLRef> original_url;
#if !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && MAC_OS_X_VERSION_MIN_REQUIRED < 1060
ATSFontRef atsFont;
FSRef fsref;
OSStatus status;
atsFont = CTFontGetPlatformFont (ct_font, NULL);
status = ATSFontGetFileReference (atsFont, &fsref);
if (status == noErr)
original_url.reset (CFURLCreateFromFSRef (NULL, &fsref));
#else
original_url.reset ((CFURLRef) CTFontCopyAttribute (ct_font, kCTFontURLAttribute));
#endif
/* Create font copy with cascade list that has LastResort first; this speeds up CoreText
* font fallback which we don't need anyway. */
{
hb_cf_ptr_t<CTFontDescriptorRef> last_resort_font_desc (get_last_resort_font_desc ());
if (last_resort_font_desc)
{
hb_cf_ptr_t<CTFontRef> new_ct_font (CTFontCreateCopyWithAttributes (ct_font, 0.0, nullptr, last_resort_font_desc));
if (new_ct_font)
{
/* The CTFontCreateCopyWithAttributes call fails to stay on the same font
* when reconfiguring the cascade list and may switch to a different font
* when there are fonts that go by the same name, since the descriptor is
* just name and size.
*
* Avoid reconfiguring the cascade lists if the new font is outside the
* system locations that we cannot access from the sandboxed renderer
* process in Blink. This can be detected by the new file URL location
* that the newly found font points to. */
hb_cf_ptr_t<CFURLRef> new_url;
#if !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && MAC_OS_X_VERSION_MIN_REQUIRED < 1060
atsFont = CTFontGetPlatformFont (new_ct_font, NULL);
status = ATSFontGetFileReference (atsFont, &fsref);
if (status == noErr)
new_url.reset (CFURLCreateFromFSRef (NULL, &fsref));
#else
new_url.reset ((CFURLRef) CTFontCopyAttribute (new_ct_font, kCTFontURLAttribute));
#endif
// Keep reconfigured font if URL cannot be retrieved (seems to be the case
// on Mac OS 10.12 Sierra), speculative fix for crbug.com/625606
if (!original_url || !new_url || CFEqual (original_url, new_url)) {
ct_font = std::move (new_ct_font);
} else {
DEBUG_MSG (CORETEXT, ct_font, "Discarding reconfigured CTFont, location changed.");
}
}
else
DEBUG_MSG (CORETEXT, ct_font, "Font copy with empty cascade list failed");
}
else
DEBUG_MSG (CORETEXT, ct_font, "Font copy with empty cascade list failed");
}
return ct_font.release ();
}
/**
* hb_coretext_face_create:
* @cg_font: The CGFontRef to work upon
*
* Creates an #hb_face_t face object from the specified
* CGFontRef.
*
* Return value: (transfer full): The new face object
*
* Since: 0.9.10
*/
hb_face_t *
hb_coretext_face_create (CGFontRef cg_font)
{
if (unlikely (!cg_font))
return hb_face_get_empty ();
hb_face_t *face = hb_face_create_for_tables (_hb_cg_reference_table, CGFontRetain (cg_font), _hb_cg_font_release);
hb_face_set_get_table_tags_func (face, _hb_cg_get_table_tags, cg_font, nullptr);
return face;
}
/**
* hb_coretext_face_create_from_file_or_fail:
* @file_name: A font filename
* @index: The index of the face within the file
*
* Creates an #hb_face_t face object from the specified
* font file and face index.
*
* This is similar in functionality to hb_face_create_from_file_or_fail(),
* but uses the CoreText library for loading the font file.
*
* Return value: (transfer full): The new face object, or `NULL` if
* no face is found at the specified index or the file cannot be read.
*
* Since: 10.1.0
*/
hb_face_t *
hb_coretext_face_create_from_file_or_fail (const char *file_name,
unsigned int index)
{
unsigned ttc_index = index & 0xFFFF;
unsigned named_instance_index = index >> 16;
if (ttc_index != 0)
{
DEBUG_MSG (CORETEXT, nullptr, "TTC index %u not supported", ttc_index);
return nullptr; // CoreText does not support TTCs
}
hb_cf_ptr_t<CFURLRef> url (CFURLCreateFromFileSystemRepresentation (nullptr,
(const UInt8 *) file_name,
strlen (file_name),
false));
if (unlikely (!url))
return nullptr;
auto ct_font_desc_array = CTFontManagerCreateFontDescriptorsFromURL (url);
if (unlikely (!ct_font_desc_array))
return nullptr;
hb_cf_ptr_t<CGFontRef> cg_font (create_cg_font (ct_font_desc_array, named_instance_index));
if (unlikely (!cg_font))
return nullptr;
hb_face_t *face = hb_coretext_face_create (cg_font);
if (unlikely (hb_face_is_immutable (face)))
{
hb_face_destroy (face);
return nullptr;
}
hb_face_set_index (face, index);
return face;
}
/**
* hb_coretext_face_create_from_blob_or_fail:
* @blob: A blob containing the font data
* @index: The index of the face within the blob
*
* Creates an #hb_face_t face object from the specified
* blob and face index.
*
* This is similar in functionality to hb_face_create_or_fail(),
* but uses the CoreText library for loading the font data.
*
* Return value: (transfer full): The new face object, or `NULL` if
* no face is found at the specified index or the blob cannot be read.
*
* Since: 11.0.0
*/
hb_face_t *
hb_coretext_face_create_from_blob_or_fail (hb_blob_t *blob,
unsigned int index)
{
hb_cf_ptr_t<CGFontRef> cg_font (create_cg_font (blob, index));
if (unlikely (!cg_font))
return nullptr;
hb_face_t *face = hb_coretext_face_create (cg_font);
if (unlikely (hb_face_is_immutable (face)))
{
hb_face_destroy (face);
return nullptr;
}
hb_face_set_index (face, index);
return face;
}
/**
* hb_coretext_face_get_cg_font:
* @face: The #hb_face_t to work upon
*
* Fetches the CGFontRef associated with an #hb_face_t
* face object
*
* Return value: the CGFontRef found
*
* Since: 0.9.10
*/
CGFontRef
hb_coretext_face_get_cg_font (hb_face_t *face)
{
return face ? (CGFontRef) (const void *) face->data.coretext : nullptr;
}
/**
* hb_coretext_font_create:
* @ct_font: The CTFontRef to work upon
*
* Creates an #hb_font_t font object from the specified
* CTFontRef.
*
* The created font uses the default font functions implemented
* natively by HarfBuzz. If you want to use the CoreText font functions
* instead (rarely needed), you can do so by calling
* by hb_coretext_font_set_funcs().
*
* Return value: (transfer full): The new font object
*
* Since: 1.7.2
**/
hb_font_t *
hb_coretext_font_create (CTFontRef ct_font)
{
if (unlikely (!ct_font))
return hb_font_get_empty ();
hb_cf_ptr_t<CGFontRef> cg_font (CTFontCopyGraphicsFont (ct_font, nullptr));
if (unlikely (!cg_font))
return hb_font_get_empty ();
hb_face_t *face = hb_coretext_face_create (cg_font);
cg_font.reset ();
hb_font_t *font = hb_font_create (face);
hb_face_destroy (face);
if (unlikely (hb_object_is_immutable (font)))
return font;
hb_font_set_ptem (font, CTFontGetSize (ct_font));
/* Copy font variations */
hb_cf_ptr_t<CFDictionaryRef> variations (CTFontCopyVariation (ct_font));
if (variations)
{
hb_vector_t<hb_variation_t> vars;
hb_vector_t<CFTypeRef> keys;
hb_vector_t<CFTypeRef> values;
CFIndex count = CFDictionaryGetCount (variations);
if (likely (vars.alloc_exact (count) && keys.resize_exact (count) && values.resize_exact (count)))
{
// Fetch them one by one and collect in a vector of our own.
CFDictionaryGetKeysAndValues (variations, keys.arrayZ, values.arrayZ);
for (CFIndex i = 0; i < count; i++)
{
int tag;
float value;
CFNumberGetValue ((CFNumberRef) keys.arrayZ[i], kCFNumberIntType, &tag);
CFNumberGetValue ((CFNumberRef) values.arrayZ[i], kCFNumberFloatType, &value);
hb_variation_t var = {(hb_tag_t) tag, value};
vars.push (var);
}
hb_font_set_variations (font, vars.arrayZ, vars.length);
}
}
/* Let there be dragons here... */
font->data.coretext.cmpexch (nullptr, (hb_coretext_font_data_t *) CFRetain (ct_font));
// https://github.com/harfbuzz/harfbuzz/pull/4895#issuecomment-2408471254
//hb_coretext_font_set_funcs (font);
return font;
}
/**
* hb_coretext_font_get_ct_font:
* @font: #hb_font_t to work upon
*
* Fetches the CTFontRef associated with the specified
* #hb_font_t font object.
*
* Return value: the CTFontRef found
*
* Since: 0.9.10
*/
CTFontRef
hb_coretext_font_get_ct_font (hb_font_t *font)
{
return font ? (CTFontRef) (const void *) font->data.coretext : nullptr;
}
#endif