7

Some unicode characters cannot be displayed on iOS but are displayed correctly on macOS. Similarly, some unicode characters that iOS can display cannot be displayed on watchOS. This is due to different built-in fonts installed on these platforms.

When a character cannot be displayed it appears as a ? inside a box, like so:
enter image description here

I've also seen some characters display as an alien instead (not sure why the difference):
enter image description here

Is there a way to know when a specific unicode character will not be displayed properly given a string of the unicode character such as "ᄥ"?

I am in need of a solution that works for both iOS and watchOS.

Jordan H
  • 52,571
  • 37
  • 201
  • 351

2 Answers2

5

You can use CTFontGetGlyphsForCharacters() to determine if a font has a glyph for a particular code point (note that supplementary characters need to be checked as surrogate pairs):

CTFontRef font = CTFontCreateWithName(CFSTR("Helvetica"), 12, NULL);
const UniChar code_point[] = { 0xD83C, 0xDCA1 };  // U+1F0A1
CGGlyph glyph[] = { 0, 0 };
bool has_glyph = CTFontGetGlyphsForCharacters(font, code_point, glyph, 2);

Or, in Swift:

let font = CTFontCreateWithName("Helvetica", 12, nil)
var code_point: [UniChar] = [0xD83C, 0xDCA1]
var glyphs: [CGGlyph] = [0, 0]
let has_glyph = CTFontGetGlyphsForCharacters(font, &code_point, &glyph, 2)

If you want to check the complete set of fallback fonts that the system will try to load a glyph from, you will need to check all of the fonts returned by CTFontCopyDefaultCascadeListForLanguages(). Check the answer to this question for information on how the fallback font list is created.

Community
  • 1
  • 1
一二三
  • 21,059
  • 11
  • 65
  • 74
  • has_glyph is false, then no possible to display correct? – NSDeveloper Jul 12 '15 at 03:34
  • If `has_glyph == false`, it will render using the Last Resort font with a box or question mark (as in the original question). – 一二三 Jul 12 '15 at 03:39
  • Very neat! Is there a more efficient way to know if any font can display a given glyph without running that code in a loop over all the fonts returned from the mentioned method? I imagine this would be very inefficient with a large number of glyphs. – Jordan H Jul 12 '15 at 06:02
  • 1
    This is essentially what the system does when it tries to render text normally, so performance shouldn't be too bad. But you will probably want to cache the `CTFont` objects, and `CTFontGetGlyphs...` can be used to check an entire string at once (look for `glyphs[i] != 0` rather than the return value). – 一二三 Jul 12 '15 at 06:44
0

Compare against the known, undefined character U+1FFF:

/// - Parameter font: a UIFont
/// - Returns: true if glyph exists
func glyphAvailable(forFont font:UIFont) -> Bool {
    if let refUnicodePng = Character("\u{1fff}").png(forFont: font),
        let myPng = self.png(forFont: font) {
        return refUnicodePng != myPng
    }
    return false
}

using a png bitmap:

/// - Parameter font: a UIFont
/// - Returns: an optional png representation
func png(forFont font: UIFont) -> Data? {
    let attributes = [NSAttributedStringKey.font: font]
    let charStr = "\(self)" as NSString
    let size = charStr.size(withAttributes: attributes)

    UIGraphicsBeginImageContext(size)
    charStr.draw(at: CGPoint(x: 0,y :0), withAttributes: attributes)

    var png:Data? = nil
    if let charImage = UIGraphicsGetImageFromCurrentImageContext() {
        png = UIImagePNGRepresentation(charImage)
    }

    UIGraphicsEndImageContext()
    return png
}

Answered here.

SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179