Search code examples
swiftcore-text

How to let CTFontCreateUIFontForLanguage work with Kanji and Emojis?


This question is related to CoreText (available among Apple platforms). The langauge is Swift 5.10.

It seems that the CTFontCreateUIFontForLanguage() doesn't return usable results if the given UniChar array contains kanji / emoji.

My attempts:

Suppose that I utilize CTFontCreateUIFontForLanguage() using the following Swift script.

#!/usr/bin/env swift

import CoreText
import Foundation

guard let osFont = CTFontCreateUIFontForLanguage(
  .system, 14, "zh-Hant" as CFString
) else {
  print("CTFont initiation failed.")
  exit(1) // Abortion.
}

extension CTFont {
  func glyphs(for string: String) -> [CGGlyph]? {
    let arrUTF16 = Array(string.utf16)
    var glyphs = [CGGlyph](repeating: 0, count: arrUTF16.count)
    let result = CTFontGetGlyphsForCharacters(self, arrUTF16, &glyphs, arrUTF16.count)
    return result ? glyphs : nil
  }
}

let string = "這是草莓蛋糕🍓🍰。" // NOTE: Edit this line to try various parameters.
let unichars = Array(string.utf16)

var fetchedGlyphs: [CGGlyph]? = osFont.glyphs(for: string)
guard var fetchedGlyphs = fetchedGlyphs else {
  print("Screwed. I don't know why it only works with ASCII glyphs.")
  exit(1) // Abortion.
}

Note that the CTFont can be replaced by NSFont.systemFont(ofSize: NSFont.systemFontSize) instead of the one I created above. CTFont and NSFont are toll-free bridgable in Swift 5.

The problem in the case:

The (CTFont instance).glyphs(for:) returns nil if the given string parameter contains kanji and / or emoji characters. One may try with other kanji-only strings or emoji-only strings.


Solution

  • You should use CTFontCreateForString, or its corresponding init. This looks up the font in the font cascade list and finds a font that supports as many characters in the given string as possible.

    let string = "你好"
    let baseFont = CTFont(.system, size: 14)
    let osFont = CTFont(font: baseFont, string: string as CFString, range: CFRange(location: 0, length: string.utf16.count))
    if let fetchedGlyphs = osFont.glyphs(for: string) {
        print(fetchedGlyphs)
    }