Determine if unicode character has a glyph in UIFont

1.9k views Asked by At

I want to find out if a certain unicode character has a glyph representation even if by a cascading font. For example, let's say I am using UIFont.systemFont(withSize:18) and a string \u{1CDA} and would like to find out if this font will display the graphical representation of this character, and not a default question mark representation (ie there's no graphical representation, even by the supporting cascade fonts).

3

There are 3 answers

1
andrewz On BEST ANSWER

This works for me. Swift 3, XCode 8.6 version:

import UIKit
import CoreText

extension Font {
    public func hasGlyph(utf32 character:UInt32) -> Bool {

        var code_point: [UniChar] = [
            UniChar.init(truncatingBitPattern: character),
            UniChar.init(truncatingBitPattern: character >> 16)
        ]
        var glyphs: [CGGlyph] = [0,0]
        let result = CTFontGetGlyphsForCharacters(self as CTFont, &code_point, &glyphs, glyphs.count)
        return result
    }
}

public class Glypher {

    let font:UIFont

    var support:[CTFont] = []

    public init(for font:UIFont, languages:[String] = ["en"]) {
        self.font = font
        let languages = languages as CFArray
        let result = CTFontCopyDefaultCascadeListForLanguages(font as CTFont, languages)
        let array = result as! Array<CTFontDescriptor>
        for descriptor in array {
            support.append(CTFontCreateWithFontDescriptor(descriptor,18,nil))
        }
    }

    public func isGlyph(_ point:UInt32) -> Bool {
        return font.hasGlyph(utf32:point) || isGlyphSupported(point)
    }

    public func isGlyphSupported(_ point:UInt32) -> Bool {
        for font in support {
            var code_point: [UniChar] = [
                UniChar.init(truncatingBitPattern: point),
                UniChar.init(truncatingBitPattern: point >> 16)
            ]
            var glyphs: [CGGlyph] = [0, 0]
            let result = CTFontGetGlyphsForCharacters(font as CTFont, &code_point, &glyphs, glyphs.count)
            if result {
                return true
            }
        }
        return false
    }
}

let glypher = Glypher(for:UIFont.systemFont(ofSize:18))
if glypher.isGlyph(0x1CDA) {
    print("bingo!")
}
2
CMash On

This is heavily based on the answer by @andrewz, so most credit to them! I found some issues with certain emojis giving false negatives (including ✋) so I've got some modifications, including passing the emoji in as a String:

(note: I've got Font instead of UIFont as I'm using this on both iOS and macOS so have a typealias to switch between the two font classes, you can just prefix it with NS or UI for your platform of choice!)

extension Font {
    
    func canRender(emoji: String) -> Bool {
        var code_points = Array(emoji.utf16)
        var glyphs: [CGGlyph] = Array(repeating: 0, count: code_points.count)
        return CTFontGetGlyphsForCharacters(self as CTFont, &code_points, &glyphs, glyphs.count)
    }
    
}

class Glypher {

    private let font: Font
    private var support: [CTFont] = []

    init(for font: Font, languages: [String] = ["en"]) {
        self.font = font
        let languages = languages as CFArray
        let result = CTFontCopyDefaultCascadeListForLanguages(font as CTFont, languages)
        let array = result as! Array<CTFontDescriptor>
        for descriptor in array {
            support.append(CTFontCreateWithFontDescriptor(descriptor, 18, nil))
        }
    }

    func isRenderable(emoji: String) -> Bool {
        return font.canRender(emoji: emoji) || renderSupported(emoji: emoji)
    }

    func renderSupported(emoji: String) -> Bool {
        var code_points = Array(emoji.utf16)
        var glyphs: [CGGlyph] = Array(repeating: 0, count: code_points.count)
        for font in support where CTFontGetGlyphsForCharacters(font as CTFont, &code_points, &glyphs, glyphs.count) {
            return true
        }
        return false
    }
    
}
0
Jason Harrison On

This may work too, it doesn't check the glyph, but it checks the character set

import CoreText
func isSupported(unicode: UnicodeScalar, font: UIFont) -> Bool {
    let coreFont: CTFont = font
    let characterSet: CharacterSet = CTFontCopyCharacterSet(coreFont) as CharacterSet
    return characterSet.contains(unicode)
 }

Example test:

let testString = "R"
let font = UIFont.boldSystemFont(ofSize: 10.0)
print("\(isSupported(unicode: testString.unicodeScalars.first!, font: font))")