Get all available characters from a font - swift

I am developing an iOS app in Swift 3.
In this app I am listing all available fonts (system provided) but I would like to list all available characters for them too.
For example I am using Font Awesome to and I want the user to be able to select any of the characters/symbols from a list. How can I do this?
This is how I get an array of the fonts. How can I get an array of all characters for a selected font?
UIFont.familyNames.map({ UIFont.fontNames(forFamilyName: $0)}).reduce([]) { $0 + $1 }

For each UIFont, you have to get characterSet of that font. For example, I take first UIFont.
let firsttFont = UIFont.familyNames.first
let first = UIFont(name: firsttFont!, size: 14)
let fontDescriptor = first!.fontDescriptor
let characterSet : NSCharacterSet = fontDescriptor.object(forKey: UIFontDescriptorCharacterSetAttribute) as! NSCharacterSet
Then, use this extension to get all characters of that NSCharacterSet:
extension NSCharacterSet {
var characters:[String] {
var chars = [String]()
for plane:UInt8 in 0...16 {
if self.hasMemberInPlane(plane) {
let p0 = UInt32(plane) << 16
let p1 = (UInt32(plane) + 1) << 16
for c:UTF32Char in p0..<p1 {
if self.longCharacterIsMember(c) {
var c1 = c.littleEndian
let s = NSString(bytes: &c1, length: 4, encoding: String.Encoding.utf32LittleEndian.rawValue)!
chars.append(String(s))
}
}
}
}
return chars
}
}
(Ref: NSArray from NSCharacterset)
So, at last, just call characterSet.characters to get all characters (in String)

I don't think you'll be able to without a lot of coding. Here's a few links to Apple documentation:
In their main font page you'll have to scroll down a bit to get to a list of documentation, but in that list is their TrueType reference manual. The characters are stored as glyphs, meaning they are vector-based to allow for clean font sizes. (I believe the simple drop-down of font sizes in IB are merely "suggestions", and you can type in any size you care to.)
In that second link, scroll down to the lengthy list of font tables. One looks promising - the cmap table. But reading through this, it's possible to (a) have foreign characters like "umlaut A" or Chinese and (b) omit characters in each font. Also, this is just a lookup table - you'll then maybe need to use the mapping table to get the location of the glyph.
If you are targeting English only, you might be better off finding a way to check if the letters "Aa" exist for the font and display them.

Related

CTFontGetGlyphsForCharacters Returns false when passing emoji

In a class conforming to NSLayoutManagerDelegate I implement this method:
func layoutManager(_ layoutManager: NSLayoutManager,
shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>,
properties props: UnsafePointer<NSLayoutManager.GlyphProperty>,
characterIndexes charIndexes: UnsafePointer<Int>,
font aFont: UIFont,
forGlyphRange glyphRange: NSRange) -> Int {
// First, make sure we'll be able to access the NSTextStorage.
guard let textStorage = layoutManager.textStorage
else { return 0 }
// Get the first and last characters indexes for this glyph range,
// and from that create the characters indexes range.
let firstCharIndex = charIndexes[0]
let lastCharIndex = charIndexes[glyphRange.length - 1]
let charactersRange = NSRange(location: firstCharIndex, length: lastCharIndex - firstCharIndex + 1)
var bulletPointRanges = [NSRange]()
var hiddenRanges = [NSRange]()
let finalGlyphs = UnsafeMutablePointer<CGGlyph>(mutating: glyphs)
// Generate the Middle Dot glyph using aFont.
let middleDot: [UniChar] = [0x00B7] // Middle Dot: U+0x00B7
var myGlyphs: [CGGlyph] = [0]
// Get glyphs for `middleDot` character
guard CTFontGetGlyphsForCharacters(aFont, middleDot, &myGlyphs, middleDot.count) == true
else { fatalError("Failed to get the glyphs for characters \(middleDot).") }
}
The problem is that CTFontGetGlyphsForCharacters returns false when I type an emoji into the textview. I think it might have something to do with UTF-8 vs. UTF-16 but I'm kind of out of my depth a little here. Little help?
The font you are using does not have a glyph for that particular character.
The system maintains a list of "font fallbacks" for times when the specific font you are trying to look at does not have a glyph but another font might.
The list of fallbacks is given by CTFontCopyDefaultCascadeListForLanguages, but since you're at the point where you are being asked for the glyph from a particular font, it seems that fallback generation should be handled higher up in the chain.
You should probably return 0 to indicate that the layout manager should use it's default behavior.

Iterate trough every word from WordsArray to take every character from it

I'm making some hangman app so words i use should be displayed with "?" instead of letters
if let wordsUrl = Bundle.main.url(forResource: "start", withExtension: "txt"){
if let wordsContent = try? String(contentsOf: wordUrl){
var allWords = wordsContent.components(separatedBy: "\n")
I don't know how to index every word from allWords array.? After that i would change letters using another property which i would use to display
for letter in word {
usedLetters.append(letter)
promptWord.append("?")
I’d recommend creating a method which you can call whenever your text field needs updating due to something such as a new letter input from the user.
var wordTextField: UITextField!
var usedWords = [] // Array to track the words already used by the user
let word = "hangman" // Word for the user to guess
var promptWord = "" // What will be displayed in the wordTextField
func updateTextField() {
for letter in word.uppercased() {
let strLetter = String(letter)
if usedLetters.contains(strLetter) {
promptWord += strLetter
} else {
promptWord += "?"
}
}
wordTextField.text = promptWord
A brief explanation of what the code does:
Firstly it iterates through the word inspecting each letter (uppercase so that there are no inconsistencies with the characters when the comparison is made to what the user has entered as their guess).
Secondly it checks to see if the strLetter is contained within the usedLetters array if it is then it places the letter inside of the correct location in the promptWord.
Whenever the letter is not found to be contained within the usedWords array a “?” is instead added to the string.
Finally the text of the wordTextField is set to be the promptWord displaying the amount of letters which the user has left to guess and how many as well as which letters the user has guessed correctly.
You can convert a String to an array of characters:
let string = "a String"
let characters = Array(characters)
So you could map your array of words to an array of arrays of characters like this:
var allWordsAsCharacterArrays = allWords.map { Array($0) }
You can also populate strings with question marks using String.init(repeating:count:)
When you pick a word from your words array, you could convert it to an array of characters, and a working string that you would populate with an array of question marks. As the user picks letters, you could replace the question marks in the working string with the correct letters from the word they are guessing.
It looks like you are just trying to provide the user ultimately with a hidden word containing only question marks. May I suggest a more straight forward approach?
let wordToGuess = "Hangman"
let hiddenWord = String(repeating: "?", count: wordToGuess.count)
now when the user guesses you can replace the proper characters
let guess = "h" // get from your user input
if wordToGuess.localizedStandardContains(guess) {
var location = 0
for c in wordToGuess {
if c.lowercased() == guess.lowercased() {
let index = hiddenWord.index(hiddenWord.startIndex, offsetBy: location)
hiddenWord = hiddenWord.replacingCharacters(in: index...index , with: String(c) )
print("hidden word now: \(hiddenWord)")
}
location += 1
}
}
note this is pretty messy code. It works, but I'm sure there is a much better way.

How to get all characters of the font with CTFontCopyCharacterSet() in Swift?

How does one get all characters of the font with CTFontCopyCharacterSet() in Swift? ... for macOS?
The issue occured when implementing the approach from an OSX: CGGlyph to UniChar answer in Swift.
func createUnicodeFontMap() {
// Get all characters of the font with CTFontCopyCharacterSet().
let cfCharacterSet: CFCharacterSet = CTFontCopyCharacterSet(ctFont)
//
let cfCharacterSetStr = "\(cfCharacterSet)"
print("CFCharacterSet: \(cfCharacterSet)")
// Map all Unicode characters to corresponding glyphs
var unichars = [UniChar](…NYI…) // NYI: lacking unichars for CFCharacterSet
var glyphs = [CGGlyph](repeating: 0, count: unichars.count)
guard CTFontGetGlyphsForCharacters(
ctFont, // font: CTFont
&unichars, // characters: UnsafePointer<UniChar>
&glyphs, // UnsafeMutablePointer<CGGlyph>
unichars.count // count: CFIndex
)
else {
return
}
// For each Unicode character and its glyph,
// store the mapping glyph -> Unicode in a dictionary.
// ... NYI
}
What to do with CFCharacterSet to retrieve the actual characters has been elusive. Autocompletion of the cfCharacterSet instance offers show no relavant methods.
And the Core Foundation > CFCharacterSet appears have methods for creating another CFCharacterSet, but not something the provides an array|list|string of unichars to be able to create a mapped dictionary.
Note: I'm looking for a solution which is not specific to iOS as in Get all available characters from a font which uses UIFont.
CFCharacterSet is toll-free bridged with the Cocoa Foundation counterpart NSCharacterSet, and can be bridged to the corresponding Swift value type CharacterSet:
let charset = CTFontCopyCharacterSet(ctFont) as CharacterSet
Then the approach from NSArray from NSCharacterSet can be used to enumerate all Unicode scalar values of that character set (including non-BMP points, i.e. Unicode scalar values greater than U+FFFF).
The CTFontGetGlyphsForCharacters() expects non-BMP characters as surrogate pair, i.e. as an array of UTF-16 code units.
Putting it together, the function would look like this:
func createUnicodeFontMap(ctFont: CTFont) -> [CGGlyph : UnicodeScalar] {
let charset = CTFontCopyCharacterSet(ctFont) as CharacterSet
var glyphToUnicode = [CGGlyph : UnicodeScalar]() // Start with empty map.
// Enumerate all Unicode scalar values from the character set:
for plane: UInt8 in 0...16 where charset.hasMember(inPlane: plane) {
for unicode in UTF32Char(plane) << 16 ..< UTF32Char(plane + 1) << 16 {
if let uniChar = UnicodeScalar(unicode), charset.contains(uniChar) {
// Get glyph for this `uniChar` ...
let utf16 = Array(uniChar.utf16)
var glyphs = [CGGlyph](repeating: 0, count: utf16.count)
if CTFontGetGlyphsForCharacters(ctFont, utf16, &glyphs, utf16.count) {
// ... and add it to the map.
glyphToUnicode[glyphs[0]] = uniChar
}
}
}
}
return glyphToUnicode
}
You can do something like this.
let cs = CTFontCopyCharacterSet(font) as NSCharacterSet
let bitmapRepresentation = cs.bitmapRepresentation
The format of the bitmap is defined in the reference page for CFCharacterSetCreateWithBitmapRepresentation

Check or validation Persian(Farsi) string swift

I searched over web pages and stack overflow about validation of a Persian(Farsi) language string. Most of them have mentioned Arabic letters. Also, I want to know if my string is fully Persian(not contain).
for example, these strings are Persian:
"چهار راه"
"خیابان."
And These are not:
"خیابان 5"
"چرا copy کردی؟"
Also, just Persian or Arabic digits are allowed. There are exceptions about [.,-!] characters(because keyboards are not supported these characters in Persian)
UPDATE:
I explained a swift version of using regex and predicate in my answer.
Based on this extension found elsewhere:
extension String {
func matches(_ regex: String) -> Bool {
return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
}
}
and construct your regex containing allowed characters like
let mystra = "چهار راه"
let mystrb = "خیابان."
let mystrc = "خیابان 5"
let mystrd = "چرا copy کردی؟" //and so on
for a in mystra {
if String(a).matches("[\u{600}-\u{6FF}\u{064b}\u{064d}\u{064c}\u{064e}\u{064f}\u{0650}\u{0651}\u{0020}]") { // add unicode for dot, comma, and other needed puctuation marks, for now I added space etc
} else { // not in range
print("oh no--\(a)---zzzz")
break // or return false
}
}
Make sure you construct the Unicode needed using the above model.
Result for other strings
for a in mystrb ... etc
oh no--.---zzzz
oh no--5---zzzz
oh no--c---zzzz
Enjoy
After a period I could find a better way:
extension String {
var isPersian: Bool {
let predicate = NSPredicate(format: "SELF MATCHES %#",
"([-.]*\\s*[-.]*\\p{Arabic}*[-.]*\\s*)*[-.]*")
return predicate.evaluate(with: self)
}
}
and you can use like this:
print("yourString".isPersian) //response: true or false
The main key is using regex and predicate. these links help you to manipulate whatever you want:
https://nshipster.com/nspredicate/
https://nspredicate.xyz/
http://userguide.icu-project.org/strings/regexp
Feel free and ask whatever question about this topic :D
[EDIT] The following regex can be used to accept Latin numerics, as they are mostly accepted in Persian texts
"([-.]*\\s*[-.]*\\p{Arabic}*[0-9]*[-.]*\\s*)*[-.]*"

How to stop NSTextView from displaying characters that are not supported in a specified font

I am looking for a way to stop an NSTextView from falling back on a cascading font when certain characters in the display string are unavailable in the specified font.
I am writing an app in which it is important that the user know if certain characters (those in East Asian character sets, for example) are available in a given font. If these characters are not available, I want the field to display these as boxes, blank spaces, or something similar. However, the default is for the textview to fall back on a cascading font in which the characters are supported. Is there any way to change this behavior?
If this is not possible, might there be some way to detect which languages are supported by a given font?
Any help would be greatly appreciated.
I was able to make it work by creating extensions partially based on this answer.
The solution I ended up using was to iterate through each character in the string in question and check to see if it is contained in the font's character set. If it is not, the appearance is changed (in this case, changing the color and adding strikethrough) before it is added to an attributed string containing all of characters of the original string.
This is what it ended up looking like:
extension UnicodeScalar {
func isIn(font:NSFont)-> Bool {
let coreFont:CTFont = font
let charSet:CharacterSet = CTFontCopyCharacterSet(coreFont) as CharacterSet
if charSet.contains(self) {
return true
}
return false
}
}
extension NSFont {
func supportString(_ str:String)-> NSAttributedString {
let attString = NSMutableAttributedString(string: "")
for scalar in str.unicodeScalars {
if !scalar.isIn(font: self) {
let r:CGFloat = 200
let g:CGFloat = 0
let b:CGFloat = 0
let a:CGFloat = 0.2
let color = NSColor(red: r, green: g, blue: b, alpha: a)
let attcha = NSMutableAttributedString(string: String(scalar), attributes: [NSAttributedStringKey.foregroundColor:color, NSAttributedStringKey.strikethroughStyle:NSUnderlineStyle.styleSingle.rawValue])
attString.append(attcha)
} else {
let attcha = NSAttributedString(string: String(scalar), attributes: [NSAttributedStringKey.foregroundColor:NSColor.black])
attString.append(attcha)
}
}
return attString
}
}