Saba
Saba

Reputation: 411

Setting font weight using CoreText and CTFontDescriptor

I'm trying to use CoreText to create fonts with a specific font weight. I'm trying to use a CTFontDescriptor that I've created with an attributes dictionary specifying the font's name and font's weight. The correct font family is getting used, however, the weight doesn't seem to be getting set. Here's my code in Swift:

let fontDescriptorAttributes = [
    kCTFontNameAttribute: "Courier",
    kCTFontTraitsAttribute: [
        kCTFontWeightTrait: NSFont.Weight.black
    ]
] as [CFString : Any]
let fontDescriptor = CTFontDescriptorCreateWithAttributes(fontDescriptorAttributes as CFDictionary)
var identityMat = CGAffineTransform(scaleX: 1.0, y: 1.0)
let font = CTFontCreateWithFontDescriptor(fontDescriptor, 24, &identityMat)

// Use the resulting font with a Core Animation text layer
let textLayer = CATextLayer()
textLayer.font = font
textLayer.string = "foo bar baz!"

textLayer.frame = CGRect(x: 0, y:0, width: textLayer.preferredFrameSize().width, height: textLayer.preferredFrameSize().height)
view.layer.addSublayer(textLayer) // add text layer to view's layer hierarchy

I've used the resulting font with both a CATextLayer and CFAttributedString but neither of the resulting graphics has the correct font weight. Should I be setting it differently?

Upvotes: 2

Views: 1728

Answers (4)

W.Kai
W.Kai

Reputation: 108

I have similar issues, seems like if we create CTFont directly from font data, font weight doesn't work if the font is a font collection(ttc/otc). It do works for single font file(ttf/otf) though.

  • CTFontManagerCreateFontDescriptorsFromData (ttc/otc)
  • CTFontManagerCreateFontDescriptorFromData (ttf/otf)

I also find If we create CTFont by creating CGFont from font data, the issues above will not exist.

But CGFont seems suffer memory leak issues.

Upvotes: 0

Matthew Rips
Matthew Rips

Reputation: 641

UPDATED ANSWER: The source of the problem is the attribute key being used to specify the font name: kCTFontNameAttribute versus kCTFontFamilyNameAttribute.

The OP used the former, kCTFontNameAttribute, which targets an exact hit on the full PostScript name for a font (e.g., Helvetica-BoldMT). When the name supplied is merely "Helvetica", the operating system understands you to mean that you want the one and only font that bears that name--that you don't want any of the other fonts with longer names that might include that name. Apparently, in some cases, the OS gives this specification a higher priority than any specification of traits for font weight or slant. Trial and error experimentation seems to yield results that are inconsistent with the behavior described in the documentation. The Apple documentation for that attribute key states:

This is the key for retrieving the PostScript name from the font descriptor. When matching, this is treated more generically: the system first tries to find fonts with this PostScript name. If none is found, the system tries to find fonts with this family name, and, finally, if still nothing, tries to find fonts with this display name. The value associated with this key is a CFStringRef. If unspecified, defaults to "Helvetica", if unavailable falls back to global font cascade list.

The latter, kCTFontFamilyNameAttribute, instead narrows the search down to any font within the specified family. From there, the OS hones in on the specific font by looking at the specified traits, if any, such as weight or slant. This key yields the results that the OP (and I) hoped to get from the other key. The Apple documentation states:

This is the key for accessing the family name from the font descriptor. The value associated with this key is a CFStringRef.

The operation and naming of these attribute keys is not intuitive. . . .

MY ORIGINAL ANSWER: This looks like a bug in the iOS/MacOS implementation of certain fonts. I find that Courier and Helvetica fonts don't seem to accept a bold weight. But, most other fonts do.

For example, using your code, with Arial or Times New Roman instead of Courier, produces the expected result: a bold font.

Have you seen the same behavior?

Upvotes: 1

Martin Winter
Martin Winter

Reputation: 1379

If you want to use kCTFontTraitsAttribute and kCTFontWeightTrait when creating the font (as opposed to adding the trait later, as mentioned by Ken Thomases), you must not use kCTFontNameAttribute as well. The documentation for the latter states that it is the PostScript name, which always includes both the font family and the font face. The documentation for +[NSFont fontWithName:size] is a bit clearer:

The value of the fontName parameter is a fully specified family-face name, preferably the PostScript name, such as Helvetica-BoldOblique or Times-Roman.

If you pass “Courier” as the name, the system interprets this as “Courier-Regular”, presumably ignoring the weight trait because you already fully specified the font face. Instead, use kCTFontFamilyNameAttribute to specify just the family “Courier” – the weight trait will then be added to the family and result in “Courier-Bold”.

Upvotes: 0

Ken Thomases
Ken Thomases

Reputation: 90661

CoreText does not synthesize weights for fonts. It doesn't/can't generate a "black" weight of Courier, it can only find one if it's available.

On my Mac, Courier has Regular and Bold weights, but no Black weight (as observed using Font Book.app). Therefore, attempting to create a Black-weighted variant of Courier won't work; it will just return one of the other weights.

So, you can't necessarily get the weight you desire. There are techniques you can use to get as close as possible, though:

You can use CTFontDescriptorCreateMatchingFontDescriptors() with an under-specified descriptor (i.e. specifies the family but not weight) and enumerate the resulting descriptors to find the best match for the desired weight.

Or, you can use CTFontDescriptorCreateCopyWithSymbolicTraits() with kCTFontTraitBold to get the closest thing the font has to a bold weight.

Upvotes: 0

Related Questions