13

Does CoreText have any facility for selecting a SmallCaps variant of a font, or for synthesizing small caps if the font doesn't have that feature? I can't find anything in the CoreText documentation that talks about small caps, though there are facilities for dealing with font variations/features. Has anyone done anything similar to this?

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
Lily Ballard
  • 182,031
  • 33
  • 381
  • 347

5 Answers5

10

The answer appears to be a qualified Yes. It supports fonts that have a Small Caps feature, but it doesn't support synthesizing Small Caps in fonts that don't have the feature. This feature can be enabled by creating a CTFontDescriptor with the kCTFontFeatureSettingsAttribute attribute, which maps to an array of feature dicts. The kCTFontFeatureTypeIdentifierKey key must be set to 3 for Letter Case, and the kCTFontFeatureSelectorIdentifierKey must be set to 3 for Small Caps. <ATS/SFNTLayoutTypes.h> contains constants that identify the various values, though this header isn't available in the iOS SDK.

Of the fonts available on the iPad, the following support Small Caps:

  • HoeflerText-Regular
  • HoeflerText-Italic
  • HoeflerText-Black
  • HoeflerText-BlackItalic
  • Didot

Note, the Italic/Bold fonts in the Didot family don't support small caps.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
6

It's generally easiest to use CTFontDescriptorCreateCopyWithFeature. As you mentioned in your own answer, this will only work for fonts that actually implement the feature you are requesting.

Ned Holbrook
  • 126
  • 3
  • In my case, I also need to optionally support italics/bold, so I do need to use the more generic attributes version. But you're right, for a simple toggling on/off of small caps, `CTFontDescriptorCreateCopyWithFeature` is slightly easier to use. – Lily Ballard Jan 27 '11 at 00:31
5

I decided to answer here to provide a more complete solution to anyone trying to solve this issue, as the info here is incomplete.

This solution uses the iOS 7 UIFontDescriptor as I am now dropping support for iOS 6.

As Anthony Mattox pointed out, the system font values (which are listed as 3 and 3 but should be noted to actually be kLetterCaseType and kSmallCapsSelector, you should not refer to an enum by its number), will not work for custom fonts. I am not sure whether this is the case for all custom fonts or just some, but I found this to be the case with mine.

When digging into the declaration of both of these enum values, you can actually see that they are deprecated anyway and presumably only work for the few system fonts that support small caps. After logging the available attributes for my custom font as outlined by Anthony, I found the 2 correct attributes to use for custom fonts. They are kLowerCaseType and kLowerCaseSmallCapsSelector. I believe that this combination is the only other option so for any font you attempt to use, it will be one or the other.

I wrote some category methods to encapsulate this functionality for both cases:

- (UIFont *) smallCapSystemFont
{
    UIFontDescriptor *descriptor = [self fontDescriptor];
    NSArray *array = @[@{UIFontFeatureTypeIdentifierKey : @(kLetterCaseType),
                         UIFontFeatureSelectorIdentifierKey : @(kSmallCapsSelector)}];
    descriptor = [descriptor fontDescriptorByAddingAttributes:@{UIFontDescriptorFeatureSettingsAttribute : array}];
    return [UIFont fontWithDescriptor:descriptor size:0];
}

- (UIFont *) smallCapCustomFont
{
    UIFontDescriptor *descriptor = [self fontDescriptor];
    NSArray *array = @[@{UIFontFeatureTypeIdentifierKey : @(kLowerCaseType),
                         UIFontFeatureSelectorIdentifierKey : @(kLowerCaseSmallCapsSelector)}];
    descriptor = [descriptor fontDescriptorByAddingAttributes:@{UIFontDescriptorFeatureSettingsAttribute : array}];
    return [UIFont fontWithDescriptor:descriptor size:0];
}

You use these by creating a font with the correct name and size and then calling one of these methods on it which will return a small cap version of that font. You will need to figure out the correct method to use for whatever small caps font you decide to use.

There is probably a clever way to figure out which one to use programmatically at runtime by checking for available types (even by just analyzing the results of that font properties array), but I have not bothered to do so as I am only using a few different fonts and a manual check is suitable for me.

Edit:

One thing I noticed is that numbers are handled separately. If you want numbers to be small capped too (which actually seems to be called "Old-style numbers" in the case of most fonts that support it), you will need that explicitly as an attribute.

Looks like it is the same for both supporting system fonts and custom fonts, unlike letters.

You would just add this dictionary to each of the arrays above:

@{UIFontFeatureTypeIdentifierKey : @(kNumberCaseType),
                         UIFontFeatureSelectorIdentifierKey : @(kLowerCaseNumbersSelector)}

Once again, for this to work the font itself actually needs to support this attribute.

Dima
  • 23,484
  • 6
  • 56
  • 83
  • This method doesn't work for iOS 7 system font (Helvetica Neue). – Legoless Mar 04 '14 at 10:17
  • @Legoless To clarify, it will not work for Helvetica Neue because that font does not come with a small caps mode. This will only work for fonts which have it baked into them. There is a list of these fonts in another answer to this question. An example of a system font this WILL work with is "Didot". Try `smallCapsSystemFont` with that one and you'll see it in action. – Dima Mar 04 '14 at 19:36
  • If you want to have small caps with Helvetica Neue you will either need to find a version of the font someone else has made that has it built in, or fake it by drawing uppercase characters of a smaller size. – Dima Mar 04 '14 at 19:38
  • The answer that has those fonts mentioned is from 2011, I'm thinking the fonts could have changed since then. But according to my research, both OS X and iOS have the same Helvetica Neue font. So how come Photoshop can easily render Small Caps in Helvetica Neue and iOS cannot? – Legoless Mar 04 '14 at 22:09
  • I don't have an answer for this. It is possible the font is actually different or Photoshop actually includes its own font. You can just run a test yourself. Log the font properties in your app using the code in the answer above mine to see which fonts have small caps and which fonts don't. You will get a nice overview of every property of the font and be able to see attributes are available and what attributes aren't. – Dima Mar 04 '14 at 22:30
  • Doing some research shows me that Adobe products also simulate small caps in certain cases, so it might just be fake. It seems that it will use small caps if the font contains it and will just simulate it otherwise, which it unfortunately does not seem to surface to the user. http://graphicdesign.stackexchange.com/questions/15596/open-type-sans-serif-fonts-with-true-small-caps – Dima Mar 04 '14 at 22:34
  • Good point. So what should we do now is the same thing as Photoshop does. Use the attributed string trick. How would you check if the font supports small caps natively? Using CoreText I assume? – Legoless Mar 05 '14 at 08:04
  • 1
    Once again you can use the logging function in the answer above this one (`CTFontCopyFeatures`). This will list the properties of a given font and you can see if small caps is one of them. (It will be obvious which one it is) – Dima Mar 05 '14 at 08:09
2

To extend Kevin Ballard's answer. The values '3' and '3' work for the system fonts, but don't seem to be universal. I'm using an external font and these values did not work.

You can log out all of the available properties with something like this:

UIFont *font = [UIFont fontWithName: fontName size: fontSize];
CFArrayRef  fontProperties  =  CTFontCopyFeatures ( ( __bridge CTFontRef ) font ) ;
NSLog(@"properties = %@", fontProperties);
CFRelease(fontProperties);

and determine what font feature and selector you'll need to enable small caps or other font features.

Dima
  • 23,484
  • 6
  • 56
  • 83
Anthony Mattox
  • 7,048
  • 6
  • 43
  • 59
1

As no one here has provided a Swift 4 sample, I'm just going to include playground code to display some small caps text in a UILabel:

//: Playground - noun: a place where people can play    
import UIKit
import CoreGraphics

let pointSize : CGFloat = 24
let fontDescriptor = UIFont(name: "HoeflerText-Regular", size: pointSize)!.fontDescriptor



let fractionFontDesc = fontDescriptor.addingAttributes(
    [
        UIFontDescriptor.AttributeName.featureSettings: [
            [
                UIFontDescriptor.FeatureKey.featureIdentifier: kLetterCaseType,
                UIFontDescriptor.FeatureKey.typeIdentifier: kSmallCapsSelector
            ]
        ]
    ] )

let label = UILabel(frame: CGRect(x: 0, y: 0, width: 500, height: 100))

label.font = UIFont(descriptor: fractionFontDesc, size:pointSize)
label.text = "Montpelier, Vermont" 
Glenn Howes
  • 5,447
  • 2
  • 25
  • 29