3

The NSFont API has two different ways to specify a weight. First, there is a struct NSFont.Weight which contains a floating point rawValue property. It's used in functions like:

NSFont.systemFont(ofSize fontSize: CGFloat, weight: NSFont.Weight) -> NSFont

Then there is another function for getting other fonts, which uses an integer.

NSFontManager.font(withFamily family: String, traits: NSFontTraitMask, 
                   weight: Int, size: CGFloat) -> NSFont?

The documentation for that function says that integers are not simply rounded versions of the floats. The weight can be in the range 0-15, with 5 being a normal weight. But:

NSFont.Weight.regular.rawValue == 0.0
NSFont.Weight.light.rawValue == -0.4000000059604645
NSFont.Weight.black.rawValue == 0.6200000047683716

I don't see any mention of how to convert between the NSFont.Weight and the integers. Maybe it's just some odd legacy API that they never clean up. Am I missing something?

Rob N
  • 15,024
  • 17
  • 92
  • 165
  • Why do you ask? Apparently there is no mapping. The new `NSFont.Weight` are "System-defined font-weight values" between -1 and 1. The old `Int` weight is "A hint for the weight desired, on a scale of 0 to 15". Have you tried them all? What is the weight? – Willeke Apr 26 '22 at 11:00
  • Even if there was a mapping, the two methods behave slightly different. The weight passed to `NSFont(descriptor: fontDescriptor.addingAttributes([.traits: [NSFontDescriptor.TraitKey.weight: weight]]), size: pointSize)` has no effect on most fonts (such as Menlo or Times New Roman), but `NSFontManager.font(withFamily:traits:weight:)` always returns the same font for low weights (apparently below 8) and a bold font for higher values (9 to 15). – Nickkk Feb 15 '23 at 17:01
  • "Why do you ask?" I'm running into the same problem. Specifically, I want code that previously uses calls like `NSFont.systemFont(ofSize: 14, weight: .semibold)` to allow for different typefaces, while retaining a similar weight. – Sören Kuklau Apr 30 '23 at 09:51

1 Answers1

1

Here's the mapping I came up with (by writing an RTF and looking at it in TextEdit's font panel), as of 13.4 Ventura:

NSFont.systemFont(ofSize: 14, weight…) calls yield (all Helvetica Neue):

.ultraLight // UltraLight
.light // Light
.thin // Thin
.medium // Medium
.semiBold // Bold
.bold // Bold
.heavy // Bold
.black // Bold

NSFontManager.shared.font(…weight: intWeight…) calls yield:

weight: 0-2 // UltraLight
weight: 3 // Thin
weight: 4-5 // Regular
weight: 6-7 // Medium
weight: 8-10 // Bold
weight: 11-15 // Condensed Black

Ergo:

extension NSFont
{
    /// Rough mapping from behavior of `.systemFont(…weight:)`
    /// to `NSFontManager`'s `Int`-based weight,
    /// as of 13.4 Ventura
    func withWeight(weight: NSFont.Weight) -> NSFont?
    {
        let fontManager=NSFontManager.shared

        var intWeight: Int

        switch weight
        {
        case .ultraLight:
            intWeight=0
        case .light:
            intWeight=2 // treated as ultraLight
        case .thin:
            intWeight=3
        case .medium:
            intWeight=6
        case .semibold:
            intWeight=8 // treated as bold
        case .bold:
            intWeight=9
        case .heavy:
            intWeight=10 // treated as bold
        case .black:
            intWeight=15 // .systemFont does bold here; we do condensed black
        default:
            intWeight=5 // treated as regular
        }

        return fontManager.font(withFamily: self.familyName ?? "",
                                traits: .unboldFontMask,
                                weight: intWeight,
                                size: self.pointSize)
    }
}
Sören Kuklau
  • 19,454
  • 7
  • 52
  • 86