Friend of DTCoreText Holger was having problems getting DTCoreText to use the bold font face of his custom font.
I want to take this opportunity to explain what the root cause for Holger’s problem was and what you need to know if you’re using custom fonts with DTCoreText.
DTCoreText has access to all the fonts that are installed on the iOS device, as well as those that you bundle with your app. Fonts are always grouped by Font Family. The individual looks are called Font Faces. Typically you will have 4 variants there: regular, bold, italic and bold+italic.
Apple’s Core Text system supports TrueType (TTF) as well as OpenType (OTF) fonts.
Bundle the Font
After copying the font files into your app, you assigning them to the target so that they get copied together with the other app resources. You have to register the font file names in your app’s info.plist so that they get loaded on app launch.
The font files go into the “Fonts provided by application” section, aka UIAppFonts.
To check if the fonts get loaded correctly you can simply create them by name, for example via this temporary code in your app delegate.
CTFontRef font = CTFontCreateWithName(CFSTR("CharterITC-Regu"), 20, NULL); NSLog(@"%@", font); |
If the font can be instantiated then the NSLog will output a description where you should see the same font name as in your creation statement.
CTFont <name: CharterITC-Regu, size: 20.000000, matrix: 0x0> CTFontDescriptor <attributes: <CFBasicHash 0x7e24f60 [0x222fb48]>{type = mutable dict, count = 1, entries => 1 : <CFString 0x14ddc40 [0x222fb48]>{contents = "NSFontNameAttribute"} = <CFString 0x7e24d90 [0x222fb48]>{contents = "CharterITC-Regu"} }
Having verified that the font is available at run time you can now proceed to use it in text.
Using the Custom Font
When somebody says “I’m using this font” then he means “I am using the faces of this font family” because he expects that DTCoreText should select the appropriate font face based on whether or not regular or bold text is required.
The selection process works by CSS styles, <b>, <strong>, <em> and <i> tags modifying a HTML tag’s fontDescriptor. When the NSAttributedString is being assembled then this local fontDescriptor is called with newMatchingFont to produce a font (or get one from cache) that matches the description.
The modern way to specify a font for text in HTML is to apply a style:
The (outdated) alternative – which is also still supported – is to specify the font face directly via the <font> tag. There the attribute name for the font family is – misleadingly – called “face”.
Debugging Font Matching
In Holger’s problem everything was set up correctly as described above, but still bold and regular text would end up using the same regular font face. Problems like this can happen if the font files come from a dubious source that didn’t take good care to set the font meta information correctly.
There’s a simple check you can perform if you run into these kinds of problems with a custom font. Create a DTCoreTextFontDescriptor from the CTFont and output the CSS style representation. Or you just set a breakpoint after the creation of the descriptor and inspect its properties.
DTCoreTextFontDescriptor *newDesc = [[DTCoreTextFontDescriptor alloc] initWithCTFont:font]; NSLog(@"%@", [newDesc cssStyleRepresentation]); |
In this example the output was:
font-family:'Charter ITC';font-size:20px;font-weight:bold;
So we see that even though we passed in the CharterITC-Regu from before this face appears to have a bold trait. This should not be and is a mistake.
If both the regular font face and the bold one have the bold font trait then Core Text prefers the regular one.
Workaround / Speed Improvement
Since font matching is a painfully slow process I implemented a global font override table in DTCoreText. This override table is seeded with the contents of DTCoreTextFontOverrides.plist which contains the basic setup suitable for most preinstalled fonts on iOS.
The override table specifies a combination of font family and bold and italic traits. When a bold face from a certain family is requested then DTCoreText can simple look up the font face name and create the CTFont from that. This is many times faster than doing a font search via Core Text.
This is why I recommend that you include this plist with your app’s resources, if only for the faster font lookup. Without it all still works, but if you have many alternating fonts then this unnecessarily slows down your app.
To get the same performance benefit for custom fonts you can simply add your custom font to the global override table. Incidentally this can also be used to work around fonts with incorrect traits. You should make these additions right after app launch, once a font has been matched it is cached and then this registration will be too late.
// these fix the bold problem [DTCoreTextFontDescriptor setOverrideFontName:@"CharterITC-Bold" forFontFamily:@"Charter ITC" bold:YES italic:NO]; [DTCoreTextFontDescriptor setOverrideFontName:@"CharterITC-BoldItal" forFontFamily:@"Charter ITC" bold:YES italic:YES]; // these are for completeness to get the faster lookup [DTCoreTextFontDescriptor setOverrideFontName:@"CharterITC-Regu" forFontFamily:@"Charter ITC" bold:NO italic:NO]; [DTCoreTextFontDescriptor setOverrideFontName:@"CharterITC-ReguItal" forFontFamily:@"Charter ITC" bold:NO italic:YES]; |
An alternative would be to add these 4 lines to the plist which you should be packaging with your app anyway. Though I slightly prefer the registration in code because if you use DTCoreText via CocoaPods you cannot modify the overrides plist file. But that’s your call.
Conclusion
When encountering font matching issues with external fonts you should first inspect the individual font faces if they possibly are having incorrectly specified traits. But in any case I recommend that you include the DTCoreText DemoApp’s font overrides plist with your app to speed up the matching. You can add your custom fonts in there or in code to have them included in the override process.
Categories: Q&A