Emoji with ZWJ sequences and variation selectors rendered incorrectly in major UIKit components with TextKit.

Originator:LvChaoShuai
Number:rdar://26013972 Date Originated:29-Apr-2016
Status:Resolved Resolved:01-Aug-2016
Product:iOS SDK Product Version:< 10.0 Beta 4
Classification: Reproducible:100%
 
Summary:
Emoji with ZWJ sequences and variation selectors rendered incorrectly in major UIKit components with TextKit.

Back to WWDC 2013, Apple releases the TextKit: http://devstreaming.apple.com/videos/wwdc/2013/223xex5xsgdfh1ergtjrqwoghbj/223/223.pdf

In this tutorial, it states that we can customize the cascade list wit the help of UIFontDescriptorCascadeListAttribute. With this approach, we can build our own emoji font per AAT reference manual (https://developer.apple.com/fonts/TrueType-Reference-Manual/) and fonttools (https://github.com/behdad/fonttools). And then use the customized emoji font as a fallback font for the system default font (say San Francisco after iOS 9) by adding it to the first attributes of the cascade list. For most of the emojis, it will be rendered correctly in major UIKit components such as UILabel, UITextView, NSAttributedString, etc. However, there are several issues:

First let's define two types of UIFont, call them font A and B.

Font A: a systemFont with our own emoji font as a fallback in the cascade list attribute, created by this code snippet:
  UIFont *_SystemFontWithEmojiFallback(UIFont *font)
  {
    UIFontDescriptor *descriptor = [font fontDescriptor];
    UIFontDescriptor *emojiDescriptor = [UIFontDescriptor fontDescriptorWithName:@"Test Color Emoji" size:font.pointSize];
    descriptor = [descriptor fontDescriptorByAddingAttributes:@{ UIFontDescriptorCascadeListAttribute: @[emojiDescriptor] }];
    return [UIFont fontWithDescriptor:descriptor size:font.pointSize];
  }
Font B: our own emoji font with system font as a fallback in the cascade list attribute, created by this code snippet:
  UIFont *_EmojiFontWithSystemFontFallback(UIFont *font)
  {
    UIFontDescriptor *descriptor = [font fontDescriptor];
    UIFontDescriptor *emojiDescriptor = [UIFontDescriptor fontDescriptorWithName:@"Test Color Emoji" size:font.pointSize];
    emojiDescriptor = [emojiDescriptor fontDescriptorByAddingAttributes:@{ UIFontDescriptorCascadeListAttribute: @[descriptor] }];
    return [UIFont fontWithDescriptor:emojiDescriptor size:font.pointSize];
  }

1. Emoji ZWJ Sequences (emoji with skintones) cannot be rendered correctly in UILabel.
  I tried the fist emoji with Emoji Modifier Fitzpatrick Type-2 to Type-6 (U+0001F44A,U+0001F3FB to U+0001F44A,U+0001F3FF):
  In Font A, all the emoji rendered incorrectly in UILabel, they falls back to the AppleColorEmoji instead our own customized emoji, which apparently doesn't follow the TextKit's UIFontDescriptorCascadeListAttribute. This can be found in the attached sample app's _label1.
  In Font B, all the emoji rendered correctly in UILabel, in our own customized emoji font, this can be found in the attached sample app's _label2.

  However, if your emoji string is start with a emoji without skintone color, then all the following emojis will be rendered correctly. Say we type a normal fist (U+0001F44A) first, then follows the original test string. In both font A and font B, all emojis can be rendered correctly. This can be found in the attached sample apps' _label3 and _label4.

2. Emoji variants cannot be rendered correctly in UILabel.
  I tried the the Heavy Black Heart Emoji (U+2764).
  In Font A, the heart rendered incorrectly in UILabel, they falls back to the AppleColorEmoji instead our own customized emoji, which apparently doesn't follow the TextKit's UIFontDescriptorCascadeListAttribute. This can be found in the attached sample app's _label1.
  In Font B, the heart rendered correctly in UILabel, in our own customized emoji font, this can be found in the attached sample app's _label2.

  However, if your emoji string is start with a emoji without skintone color, then all the following emojis will be rendered correctly. Say we type a normal fist (U+0001F44A) first, then follows the original test string. In both font A and font B, all emojis can be rendered correctly. This can be found in the attached sample apps' _label3 and _label4.  

3. Emoji variants cannot be rendered correctly in UITextView.
  I tried the the Heavy Black Heart Emoji (U+2764).
  In Font A, the heart rendered incorrectly in UITextView, they falls back to the AppleColorEmoji instead our own customized emoji, which apparently doesn't follow the TextKit's UIFontDescriptorCascadeListAttribute. This can be found in the attached sample app's _textView1, _textView3.
  In Font B, the heart rendered incorrectly in UITextView, they falls back to the AppleColorEmoji instead our own customized emoji, which apparently doesn't follow the TextKit's UIFontDescriptorCascadeListAttribute. This can be found in the attached sample app's _textView2, _textView4.

4. Emoji ZWJ Sequences cannot be rendered correctly in UITextView.
  I tried the the Kiss Man Man Emoji (U+1F468,U+200D,U+2764,U+FE0F,U+200D,U+1F48B,U+200D,U+1F468) and Kiss Woman Woman Emoji (U+1F469,U+200D,U+2764,U+FE0F,U+200D,U+1F48B,U+200D,U+1F469), reference: http://unicode.org/emoji/charts/emoji-zwj-sequences.html.
  In Font A, the emojis rendered incorrectly in UITextView, they falls back to the AppleColorEmoji instead our own customized emoji, which apparently doesn't follow the TextKit's UIFontDescriptorCascadeListAttribute. This can be found in the attached sample app's _textView1, _textView3.
  In Font B, the emojis rendered incorrectly in UITextView, they falls back to the AppleColorEmoji instead our own customized emoji, which apparently doesn't follow the TextKit's UIFontDescriptorCascadeListAttribute. This can be found in the attached sample app's _textView2, _textView4.

Ideally, the font A is what we want to use in product, however, it breaks in UILabel as well as NSAttributedString, so we are using font B as a workaround. To our surprise, per issue3 stated previously, it breaks in UITextView. So with multiple reasons, we don't have a fully workaround to have all emojis fall back to our own customized emoji with TextKit.

Steps to Reproduce:
1. Open the Test Emoji Fallback.xcodeproj
2. Build and run sample app on iOS 9.3 (or previous versions)


Expected Results:
All emojis lines in the sample app rendered in customized emoji font like Label 2, 3, 4.

Actual Results:
See there are 8 lines and they shows the problems stated in the description.
Heart emoji in Label 1 is broken, not rendered in our own emoji font.
Fist emoji with skintone in Label 1 is broken, not rendered in our own emoji font.
Heart emoji in TextView 5, 6, 7, 8 are broken, not rendered in our own emoji font.


Version:
iOS 9.3

Notes:
This issue exists for UILabel, NSAttributedString (draw by coreText), UITextView. And is not OS specific or device specific..

Configuration:
iPhone 6s Plus

Attachments:
'Test Emoji Fallback.zip' was successfully uploaded.

Screenshot:
http://i68.tinypic.com/23lig6g.png

Contact:
lvchaoshuai@qq.com

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!