How can I find a font that contains a given character? - ios

I have a font that I use for my app which only uses english characters. I use CoreText to display the text, and I notice that whenever someone enters a character that isn't included in the font (like an Arabic character), then the program hangs at this line:
CTFramesetterCreateFrame(textFramesetter, CFRangeMake(0, 0), textMutablePath, NULL);
I have a couple of questions:
is it possible to know if a certain character is included in a font?
is it possible for the system to find a font that contains the unknown character?
Related: Check if certain character is supported by UIFont

1) Is it possible to know if a certain character is included in a font?
BOOL FontContainsCharacter(UIFont *font, unichar character) {
NSCharacterSet *characterSet = [font.fontDescriptor objectForKey:UIFontDescriptorCharacterSetAttribute];
return [characterSet characterIsMember:character];
}
2) Is it possible for the system to find a font that contains the unknown character?
NSArray *FontDescriptorsForFontsContainingCharactersInString(NSString *string) {
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:string];
NSDictionary *fontAttributes = #{UIFontDescriptorCharacterSetAttribute:characterSet};
UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:fontAttributes];
return [fontDescriptor matchingFontDescriptorsWithMandatoryKeys:nil];
}
For example:
NSLog(#"%d", FontContainsCharacter([UIFont fontWithName:#"Helvetica" size:12], 0x0041)); // "A"
NSLog(#"%d", FontContainsCharacter([UIFont fontWithName:#"Helvetica" size:12], 0x4F60)); // "你"
for (UIFontDescriptor *fontDescriptor in FontDescriptorsForFontsContainingCharactersInString(#"你好")) {
NSLog(#"%#", [UIFont fontWithDescriptor:fontDescriptor size:12].fontName);
}
Output:
1
0
STHeitiSC-Light
STHeitiSC-Medium
STHeitiTC-Light
STHeitiTC-Medium
HiraKakuProN-W3
HiraKakuProN-W6
HiraMinProN-W3
HiraMinProN-W6

I found the answer to my first question and is based on a response from Rob Mayoff in this other question: CTFontGetGlyphsForCharacters always returns false
- (BOOL) textExistsInFont:(NSString *)text
{
CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)self.fontName, self.fontSize, NULL);
NSUInteger count = text.length;
unichar characters[count];
[text getCharacters:characters range:NSMakeRange(0, count)];
CGGlyph glyphs[count];
if (CTFontGetGlyphsForCharacters(fontRef, characters, glyphs, count) == false)
{
return NO;
}
else
{
return YES;
}
}
My second question I'm still working on but am getting the impression that the Apple system font (Helvetica Neue) covers many languages. Want to verify though. So if the character isn't in the font you're using, then switching to the system font might be a viable alternative.

Related

Cannot load specific icon from `SAP-icons.ttf` font

I am trying to display SAP icon(please see link to the attachment) from custom font. I've added custom font SAP-icons.ttf to the project(it's included in bundle and loaded correctly, double checked), but I cannot load the correct symbol.
The code snippet that I use is below:
Get path to the font. It's stored in specific bundle. Get reference using CGDataProvider.
NSString *fontPath = [[NSBundle my_currentBundle] pathForResource:#"SAP-icons" ofType:#"ttf"];
CGDataProviderRef provider = CGDataProviderCreateWithFilename([fontPath UTF8String]);
CGFontRef fontRef = CGFontCreateWithDataProvider(provider);
CFErrorRef error = nil;
if (! CTFontManagerRegisterGraphicsFont(fontRef, &error)) {
CFStringRef errorDescription = CFErrorCopyDescription(error);
NSLog(#"Failed to load font: %#", errorDescription);
CFRelease(errorDescription);
}
Get font name and create UIFont object.
NSString *fontName = (NSString *)CFBridgingRelease(CGFontCopyPostScriptName(fontRef));
UIFont *font = [UIFont fontWithName:fontName size:20];
CFRelease(fontRef);
CFRelease(provider);
For desired icon I found &#xe202 referenced unicode in site with the link below:
https://sapui5.hana.ondemand.com/iconExplorer.html#/?tab=grid&search=info&icon=message-information
If &#xe202 unicode value is incorrect for this icon which should I use?
I can't event preview installed SAP-icons font characters in my Mac - all of them are question marks!
char *iconUnicodeStr = "&#xe202";
NSString *infoIconString = [NSString stringWithCString: iconUnicode encoding:(NSUnicodeStringEncoding)];
Apply specific font, set text. Result - incorrect symbol.
[self.button.titleLabel setFont:font];
[self.button.titleLabel setTitle:infoIconString];
Thanks in advance for any help and suggestions how could I do that!
Instead of
char *iconUnicodeStr = "U+xe202";
label.text = [NSString stringWithCString:iconUnicodeStr encoding:(NSUnicodeStringEncoding)];
Use
label.text = [NSString stringWithFormat:#"%C", (unichar)0xe202];
And not to forget set the font for the label
label.font = myCustomFont;
I am sure problem lies in the font name.
for (NSString *familyName in [UIFont familyNames]){
NSLog(#"Family name: %#", familyName);
for (NSString *fontName in [UIFont fontNamesForFamilyName:familyName]) {
NSLog(#"--Font name: %#", fontName);
}
}
Run above code in AppDelegate didFinish and make sure you are using correct font name.
Search for SAP_icons in the NSLog from above code. If you don't find look for the proper name.
Search for SAP in above log, that will give you correct name.

How to detect emoji and change font size

I have text which contains emoji in it, we are able to display it correctly by doing encoding and decoding the string, what I need to achieve is to increase the font size of only emoji in the text like in image below,
I have got an idea to determine the range of all emoji, and supply in NSAttributedString with increased font size. Now am out of idea how can I detect range of emojis in a given string?
Thanks
I have done the same like
let string = "This is emoji Test"
let attributedEmoji = NSMutableAttributedString(string: " \u{1F600}", attributes: [NSFontAttributeName:UIFont.systemFontOfSize(60)])
let attribString = NSMutableAttributedString.init(string: string)
attribString.appendAttributedString(attributedEmoji)
lblEmoji.attributedText = attribString
You can change the font and font size to scale the emoji.
Put all possible Emoji's(Your application uses) into an array.
Search for emoji into string from array.If found apply attributed Emoji.
Write a method that accept emoji code and return attributed emoji text.
Hope this info will help you in better way.
https://github.com/woxtu/NSString-RemoveEmoji
Find out if Character in String is emoji?
you can use it directly like below or
if ([myString containsString:#"😋"])
{
NSLog(#"one");
//change the font size here.
}
else
{
NSLog(#"fk");
//change the font size here.
}
or you can use
[mystring is isEqualToString:"I believe 😋"];
try those. hope this will help to you.
I have made one demo, You can detect emoji from the string like below,
NSString *str = #"this is 😄 and test 😊";
NSArray *arr = [str componentsSeparatedByString:#" "];
for (int i = 0; i < arr.count; i++) {
NSString *temp = [arr objectAtIndex:i];
if ( ![temp canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSLog(#"%d",i);
NSLog(#"%#",temp); // temp is emoji. You can detect emoji here from your string now you can manage as per your need
}
}
Thanks to all who answered, but none was complete answer though #Raj's suggestion to look NSString-RemoveEmoji helped me to achieve the solution for this, here it is, it works for any kind of emoji
-(NSMutableAttributedString *)getAttributedEmojiString:(NSString *)inputString{
NSMutableArray *__block emojiRange=[[NSMutableArray alloc] init];
[inputString enumerateSubstringsInRange:NSMakeRange(0, [inputString length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock: ^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop) {
if([substring isEmoji]){
[emojiRange addObject:#{#"startrange":#(substringRange.location),#"endrange":#(enclosingRange.length)}];
}
}];
NSMutableAttributedString *mutString=[[NSMutableAttributedString alloc] initWithString:inputString];
[mutString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16.0] range:NSMakeRange(0, mutString.length)];
[emojiRange enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[mutString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:35.0] range:NSMakeRange([obj[#"startrange"] floatValue], [obj[#"endrange"] floatValue])];
}];
return mutString;
}
Description
First find NSRange of all the emoji in the string by using NSString-RemoveEmoji function isEmoji, and store in array.
Supply the fetched range to apply bigger FONT SIZE to characters in the range.
Finally assign the generated attributed text to the label.
self.label.attributedText=[self getAttributedEmojiString:EmojiDecoded(originalText)];
I use two macros to Encode and Decode Emoji's since I need to save these values to server and read through api, below are the macros.
#define Encoded(val) [[val dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0]
#define Decoded(val) [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:val options:0] encoding:NSUTF8StringEncoding]
#define EmojiEncoded(val) [[NSString alloc] initWithData:[val dataUsingEncoding:NSNonLossyASCIIStringEncoding] encoding:NSUTF8StringEncoding]
#define EmojiDecoded(val) [[NSString alloc] initWithData:[val dataUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding]
Hope it helps anyone who is looking for similar solution.
Cheers, and thanks to all.
This is somewhat late but might be useful for other folks who stumble upon this answer. The secret is to ask Core Text and it knows which characters in the NSAttributedString are emoji characters.
// Build the attributed string as needed
let ns = NSAttributedString(string: s)
// Now create a typesetter and render the line
let typesetter = CTTypesetterCreateWithAttributedString(nsa)
let line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, nsa.length))
// Once you have a line you can enumerate the runs
guard let runs = CTLineGetGlyphRuns(line) as? [CTRun] else {
throw NSError(domain: "CoreText", code: -1, userInfo: nil)
}
// Each run will have a font with specific attributes
print("There are \(runs.count) run(s) in \(ns.string)")
print()
for run in runs {
let charRange = CTRunGetStringRange(run)
let x: NSAttributedString = CFAttributedStringCreateWithSubstring(nil, nsa, charRange)
print(" Chars: '\(x.string)'")
let attributes: NSDictionary = CTRunGetAttributes(run)
let font = attributes["NSFont"] as! CTFont
let traits = CTFontGetSymbolicTraits(font)
print(" Emoji == \(traits.contains(.traitColorGlyphs))")
print()
}

Add attributes to all emoji in an NSAttributedString?

The font I'm using in my iOS app has an unfortunate quality: its characters are unusually small, and as a result, if a user types in a string which includes emoji (or possibly other characters not included in the font? Haven't tested that), when iOS draws those glyphs in the AppleColorEmoji font they come out huge relative to the other glyphs.
This is of course complicated by the fact that emoji are "two-part" glyphs so I can't just do a simple for-each-character-in-the-string loop.
What I need is a method along the lines of
-(NSAttributedString *)attributedStringByAddingAttributes:(NSArray *)attrs toString:(NSString*)myString forCharactersNotInFont:(UIFont *)font
... Or failing that, at least
-(NSAttributedString *)attributedStringByAddingAttributes:(NSArray *)attrs toStringForEmoji:(NSString*)myString
...or something.
Not sure of the best way to do this.
Code I ended up with, using code adapted from here:
- (BOOL)isEmoji:(NSString *)str {
const unichar high = [str characterAtIndex: 0];
// Surrogate pair (U+1D000-1F77F)
if (0xd800 <= high && high <= 0xdbff) {
const unichar low = [str characterAtIndex: 1];
const int codepoint = ((high - 0xd800) * 0x400) + (low - 0xdc00) + 0x10000;
return (0x1d000 <= codepoint && codepoint <= 0x1f77f);
// Not surrogate pair (U+2100-27BF)
} else {
return (0x2100 <= high && high <= 0x27bf);
}
}
// The following takes a string s and returns an attributed string where all the "special characters" contain the provided attributes
- (NSAttributedString *)attributedStringForString:(NSString *)s withAttributesForEmoji:(NSDictionary *)attrs {
NSMutableAttributedString *as = [[NSMutableAttributedString alloc] initWithString:#""];
NSRange fullRange = NSMakeRange(0, [s length]);
[s enumerateSubstringsInRange:fullRange
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange,
NSRange enclosingRange, BOOL *stop)
{
if ([self isEmoji:substring]) {
[as appendAttributedString:[[NSAttributedString alloc] initWithString:substring
attributes:attrs]];
} else {
[as appendAttributedString:[[NSAttributedString alloc] initWithString:substring]];
}
}];
return as;
}
So far, seems to work quite nicely!

How to find and apply two fonts in a label in IOS

I know to apply two fonts in a label for single word with one font and rest with another font using below code..
int lengthofname#"Anand";
UIFont *boldFont = [UIFont fontWithName:#"BodoniStd" size:15];
UIFont *regularFont = [UIFont fontWithName:#"BodoniStd-BoldItalic" size:15];
//UIColor *foregroundColor = [UIColor clearColor];
// Create the attributes
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
boldFont, NSFontAttributeName,nil];
//NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
// boldFont, NSFontAttributeName,
// foregroundColor, NSForegroundColorAttributeName, nil];
NSDictionary *subAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
regularFont, NSFontAttributeName, nil];
const NSRange range = NSMakeRange(0,lengthofname);
// range of " 2012/10/14 ". Ideally this should not be hardcoded const
// Create the attributed string (text + attributes)
NSMutableAttributedString *attributedText =
[[NSMutableAttributedString alloc] initWithString:#"Anand is good programmer"
attributes:attrs];
[attributedText setAttributes:subAttrs range:range];
// Set it in our UILabel and we are done!
[self.descLabel setAttributedText:attributedText];
My requirement is to find some x words in a label and then apply Bold font and rest of text with Regular font..
Please suggest any ideas. Thanks in Advance..!
In this example there highlighted words are not funded with regular expression but I believe there is some list (NSArray: STANDARD, EXPRESS, NEXT DAY, etc) of keywords. And what you have to do is enumerate that array to find the rang in text and if founded apply the different font style, something like that:
for (NSString * keyword in listOfKeywordArray) {
NSRange range = [longTextString rangeOfString:#"keyword"];
if (range.location != NSNotFound) {
//Keyword found, apply different font
// This of course needs to be changed to apply font you want
[attrString addAttribute:NSFontAttributeName value:fontName range:range];
}
}
Greg's answer is pretty close. If you have an array of keywords, you can use for.. in to loop through the array of keywords. Then you'd need to use an inner loop with rangeOfString:options:range to find all occurrences of a given keyword.
That method returns an NSRange. You could use setAttributes:range: on the range to set the text attributes of each occurrence of each keyword to use the font and style you want. (Greg's code using addAttribute only lets you set a single attribute on the text. setAttributes:range: and addAttributes:range: both let you specify a whole dictionary of attributes, like in the code you posted. Your code might look like this:
//Whatever font attributes you want to use
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
boldFont, NSFontAttributeName,nil];
NSArray *keywords = [#"STANDARD", #"EXPRESS", #"NEXT DAY"];
//Loop through each keyword in the array of keywords.
for (NSString aKeyword in keywords)
{
//Set the range to the whole string to start with.
NSRange range = NSMakeRange(0, longTextString.length];
//While there are still more occurrences of this keyword
//Not that the code both assigns the result to range, and evaluates the location.
//This is a bit of C language sleight-of-hand. It works, but is odd-looking code.
while ((range = [longTextString
rangeOfString: aKeyword
options: NSLiteralSearch
range: range]).location != NSNotFound)
{
//Set the attributes on this occurrence of this keyword.
[longTextString setAttributes: attrs range: range];
range.location = range.location+range.length;
range.length = longTextString - range.location;
}
Disclaimer: I typed out the code above in an editor window. It may not compile, much less run. It's intended for illustration purposes only. You will need to clean it up and adapt it to your needs.
Also note that rangeOfString:options:range is not a great choice for this problem, since it will detect word fragments in the middle of longer words. For example, if one of your keywords is "The" then it would detect "The" in the first 3 characters of "Them" and "These". It should really be rewritten to use regular expression string matching that requires a string to be a whole word.
Edit #2. I decided to actually code this up. The rangeOfString:options:range: approach is unsatisfactory because, as mentioned above, it detects word fragments inside larger words. Regular Expressions are a much better solution. Here is code that marks all occurrences of an array of words as bold:
NSMutableAttributedString *viewStyledText = [theTextView.attributedText mutableCopy];
NSString *viewText = viewStyledText.string;
if (viewText.length == 0)
return;
BOOL changed = NO;
NSArray *wordsToBold = #[#"The", #"for", #"to", #"league"];
UIFont *boldFont = [UIFont boldSystemFontOfSize: 15];
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
boldFont, NSFontAttributeName,nil];
//Loop through each keyword in the array of keywords.
for (NSString *wordToBold in wordsToBold)
{
//Set the range to the whole string to start with.
NSRange range = NSMakeRange(0, viewText.length);
/*
Create a regular expression string for the current word.
The "(?i) prefix tells the regular expression to be case-insenstive. Remove it if
you want your search to be case-sensitive.
The "\b" bits (with double backslashes so the output contains a backslash) cause
The regular expression to only match whole words.
*/
NSString *wordRegExString = [NSString stringWithFormat: #"(?i)\\b%#\\b", wordToBold];
//Now create a regular expression object using the current regular expression string.
NSRegularExpression *wordRegEx = [NSRegularExpression
regularExpressionWithPattern: wordRegExString
options: 0
error: nil];
//While there are still more occurrences of this keyword...
//Not that the code both assigns the result to range, and evaluates the location.
//This is a bit of C language sleight-of-hand. It works, but is odd-looking code.
while ((range = [wordRegEx rangeOfFirstMatchInString: viewText
options: 0
range: range]).location != NSNotFound)
{
//Set the attributes on this occurrence of this keyword.
changed = YES;
[viewStyledText setAttributes: attrs range: range];
range.location = range.location+range.length;
range.length = viewStyledText.length - range.location;
}
}
if (changed)
theTextView.attributedText = viewStyledText;

Replace the truncation ellipsis of UILabel in iOS 7

How can I replace the truncation ellipsis ("…") of a UILabel in iOS 7 with another attributed character? For example, with a colored ">".
I was hoping Text Kit's NSLayoutManager would make this possible, but it appears UILabel doesn't make it public if it uses it.
Also, can I safely assume that an ellipsis is used as the truncation character in every localisation? Maybe different languages have different truncation characters.
I recommend you use TTTAttributedLabel, just set property "attributedTruncationToken" to your custom string.
I don't think it gives you access to this. I think you would have do handle it manually. For example, use TextKit to determine the size of your string, if it doesn't fit in the available area, truncate it yourself and append a ">" and then put your new string in the label.
NSAttributedString has methods for getting the size of the string.
Let me know if you need any more detail on this..?
I think you can do some customization in -replaceElipsesForLabel method provided by Fonix to get your desired result.
I have written a method to do it, and works in iOS7
-(void)setCustomEllipsis:(NSString*)customEllipsis inLabel:(UILabel*)label with:(NSString*)string{
//Replace the ellipsis
NSMutableString* result = [[NSMutableString alloc] initWithString:#""];
NSArray* strings = [string componentsSeparatedByString:#" "];
for (NSString* s in strings) {
CGRect newSize = [[NSString stringWithFormat:#"%#%#%#",result,s,customEllipsis] boundingRectWithSize:CGSizeMake(label.frame.size.width,0) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:label.font} context:nil];
if (newSize.size.height < label.frame.size.height) {
[result appendString:s];
[result appendString:#" "];
}else{
[result appendString:customEllipsis];
break;
}
}
[label setText:result];
//Set different font to the ellipsis
const CGFloat fontSize = 13;
UIFont *boldFont = [UIFont boldSystemFontOfSize:fontSize];
UIFont *regularFont = [UIFont systemFontOfSize:fontSize];
UIColor *foregroundColor = [UIColor lightGrayColor];
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:regularFont, NSFontAttributeName,foregroundColor, NSForegroundColorAttributeName, nil];
NSDictionary *subAttrs = [NSDictionary dictionaryWithObjectsAndKeys:boldFont, NSFontAttributeName, nil];
const NSRange range = [label.text rangeOfString:customEllipsis];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:result
attributes:attrs];
[attributedText setAttributes:subAttrs range:range];
[label setAttributedText:attributedText];
}

Resources