fix(apple): support language hints, create font from tables (#11807) 30fcb84a44 Co-authored-by: David Skuza <david@rive.app>
diff --git a/.rive_head b/.rive_head index 0667dcf..d3a3edd 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -b5b724b93abda0d850dc718951763c138044f3ee +30fcb84a44562a8e29c7e88ecbdc39cc944b7ba7
diff --git a/src/text/font_hb_apple.mm b/src/text/font_hb_apple.mm index 83bce58..de0b15c 100644 --- a/src/text/font_hb_apple.mm +++ b/src/text/font_hb_apple.mm
@@ -22,15 +22,16 @@ class CoreTextHBFont : public HBFont { private: + CTFontRef m_ctFont; bool m_useSystemShaper; - uint16_t m_weight; - uint8_t m_width; public: CoreTextHBFont(hb_font_t* font, + CTFontRef ctFont, bool useSystemShaper, uint16_t weight, uint8_t width); + ~CoreTextHBFont() override; void shapeFallbackRun(rive::SimpleArrayBuilder<rive::GlyphRun>& gruns, const rive::Unichar text[], @@ -42,12 +43,87 @@ rive::RawPath getPath(rive::GlyphID glyph) const override; }; +struct HBCTFaceData +{ + CTFontRef ctFont; +}; + +static void hb_ct_face_data_destroy(void* userData) +{ + auto data = static_cast<HBCTFaceData*>(userData); + if (data != nullptr) + { + if (data->ctFont != nullptr) + { + CFRelease(data->ctFont); + } + delete data; + } +} + +static hb_blob_t* hb_ct_reference_table(hb_face_t*, + hb_tag_t tag, + void* userData) +{ + auto data = static_cast<HBCTFaceData*>(userData); + if (data == nullptr || data->ctFont == nullptr) + { + return hb_blob_get_empty(); + } + + CFDataRef table = CTFontCopyTable(data->ctFont, + static_cast<CTFontTableTag>(tag), + kCTFontTableOptionNoOptions); + if (table == nullptr) + { + return hb_blob_get_empty(); + } + + const char* bytes = reinterpret_cast<const char*>(CFDataGetBytePtr(table)); + const auto length = static_cast<unsigned int>(CFDataGetLength(table)); + + return hb_blob_create(bytes, + length, + HB_MEMORY_MODE_READONLY, + (void*)table, + [](void* blobUserData) { + CFRelease(static_cast<CFDataRef>(blobUserData)); + }); +} + +static hb_font_t* hb_font_create_from_ct_tables(CTFontRef ctFont) +{ + if (ctFont == nullptr) + { + return nullptr; + } + + auto* faceData = new HBCTFaceData{(CTFontRef)CFRetain(ctFont)}; + hb_face_t* face = hb_face_create_for_tables( + hb_ct_reference_table, faceData, hb_ct_face_data_destroy); + if (face == nullptr) + { + hb_ct_face_data_destroy(faceData); + return nullptr; + } + + const unsigned int upem = (unsigned int)CTFontGetUnitsPerEm(ctFont); + if (upem > 0) + { + hb_face_set_upem(face, upem); + } + + hb_font_t* font = hb_font_create(face); + hb_face_destroy(face); + return font; +} + rive::rcp<rive::Font> HBFont::FromSystem(void* systemFont, bool useSystemShaper, uint16_t weight, uint8_t width) { - auto ctFont = (CTFontRef)systemFont; + CTFontRef ctFont = (CTFontRef)systemFont; bool isCopy = false; if (CTFontGetSize(ctFont) != kStdScale) { @@ -57,15 +133,25 @@ CTFontCreateCopyWithAttributes(ctFont, kStdScale, nullptr, nullptr); isCopy = true; } - auto font = hb_coretext_font_create(ctFont); - if (isCopy) + + hb_font_t* font = hb_font_create_from_ct_tables(ctFont); + if (font == nullptr) { - CFRelease(ctFont); + font = hb_coretext_font_create(ctFont); } if (font) { - return rive::rcp<rive::Font>( - new CoreTextHBFont(font, useSystemShaper, weight, width)); + auto riveFont = rive::rcp<rive::Font>( + new CoreTextHBFont(font, ctFont, useSystemShaper, weight, width)); + if (isCopy) + { + CFRelease(ctFont); + } + return riveFont; + } + if (isCopy) + { + CFRelease(ctFont); } return nullptr; } @@ -227,13 +313,30 @@ } } +static bool ct_extract_glyph_path(CTFontRef ctFont, + rive::GlyphID glyph, + rive::RawPath* outPath) +{ + if (ctFont == nullptr) + { + return false; + } + AutoCF<CGPathRef> cgPath = CTFontCreatePathForGlyph(ctFont, glyph, nullptr); + if (!cgPath) + { + return false; + } + CGPathApply(cgPath.get(), outPath, apply_element); + return true; +} + CoreTextHBFont::CoreTextHBFont(hb_font_t* font, + CTFontRef ctFont, bool useSystemShaper, uint16_t weight, uint8_t width) : + m_ctFont(ctFont ? (CTFontRef)CFRetain(ctFont) : nullptr), m_useSystemShaper(useSystemShaper), - m_weight(weight), - m_width(width), HBFont(font) { hb_variation_t variation_data[2]; @@ -244,6 +347,14 @@ hb_font_set_variations(font, variation_data, 2); } +CoreTextHBFont::~CoreTextHBFont() +{ + if (m_ctFont) + { + CFRelease(m_ctFont); + } +} + void CoreTextHBFont::shapeFallbackRun( rive::SimpleArrayBuilder<rive::GlyphRun>& gruns, const rive::Unichar text[], @@ -259,14 +370,16 @@ return; } - CTFontRef ctFont = hb_coretext_font_get_ct_font(m_font); + CTFontRef ctFont = m_ctFont; AutoUTF16 utf16(&text[textStart], textRun.unicharCount); + CFIndex utf16Length = + rive::math::lossless_numeric_cast<CFIndex>(utf16.array.size()); - assert(utf16.array.size() == textRun.unicharCount); + assert(utf16.array.size() >= textRun.unicharCount); AutoCF<CFStringRef> string = CFStringCreateWithCharactersNoCopy( - nullptr, utf16.array.data(), utf16.array.size(), kCFAllocatorNull); + nullptr, utf16.array.data(), utf16Length, kCFAllocatorNull); AutoCF<CFMutableDictionaryRef> attr = CFDictionaryCreateMutable(kCFAllocatorDefault, @@ -276,10 +389,11 @@ CFDictionaryAddValue(attr.get(), kCTFontAttributeName, ctFont); AutoCF<CFMutableAttributedStringRef> attrString = - CFAttributedStringCreateMutable(kCFAllocatorDefault, - textRun.unicharCount); + CFAttributedStringCreateMutable(kCFAllocatorDefault, utf16Length); CFAttributedStringReplaceString( attrString.get(), CFRangeMake(0, 0), string.get()); + CFAttributedStringSetAttributes( + attrString.get(), CFRangeMake(0, utf16Length), attr.get(), false); AutoCF<CFNumberRef> level_number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &textRun.level); @@ -355,18 +469,10 @@ // too. if (m_useSystemShaper) { - CTFontRef ctFont = hb_coretext_font_get_ct_font(m_font); - if (ctFont) + rive::RawPath ctPath; + if (ct_extract_glyph_path(m_ctFont, glyph, &ctPath)) { - AutoCF<CGPathRef> cgPath = - CTFontCreatePathForGlyph(ctFont, glyph, nullptr); - - if (cgPath) - { - rive::RawPath rpath; - CGPathApply(cgPath.get(), &rpath, apply_element); - return rpath; - } + return ctPath; } } @@ -376,18 +482,10 @@ // glyphs. Try getting them from the system. if (rpath.empty() && !m_useSystemShaper) { - CTFontRef ctFont = hb_coretext_font_get_ct_font(m_font); - if (ctFont) + rive::RawPath ctPath; + if (ct_extract_glyph_path(m_ctFont, glyph, &ctPath)) { - AutoCF<CGPathRef> cgPath = - CTFontCreatePathForGlyph(ctFont, glyph, nullptr); - - if (cgPath) - { - rive::RawPath rpath; - CGPathApply(cgPath.get(), &rpath, apply_element); - return rpath; - } + return ctPath; } } return rpath;