I tried to load font using CTFontCreateWithFontDescriptor() and it behaves differently than loaded using NSFont(). Let's say the goal is to load font using CoreText that is exactly the same as using AppKit.
NSTextField
If I use them in NSTextField text offsets and rendering do not match.
let familyName = "Helvetica"
let fontSize: CGFloat = 30
let string = "Hello, World! gÖ,|#"
// Load font using AppKit
let font1 = NSFont(name: familyName, size: fontSize)!
// Load font using CoreText API
let font2 = CTFontCreateWithFontDescriptor(CTFontDescriptorCreateWithAttributes([
kCTFontFamilyNameAttribute: familyName,
kCTFontSizeAttribute: fontSize,
] as [CFString : Any] as CFDictionary), 0, nil) as NSFont
// Setup text fields
let field1 = NSTextField()
let field2 = NSTextField()
field1.setAsLabel()
field2.setAsLabel()
field1.font = font1
field2.font = font2
field1.stringValue = string
field2.stringValue = string
NSAttributedString
If I use them in NSAttributedString bounding box height of text is different:
let bounds1 = NSAttributedString(string: string, attributes: [
.font: font1
]).boundingRect(with: .infinity)
let bounds2 = NSAttributedString(string: string, attributes: [
.font: font2
]).boundingRect(with: .infinity)
print(bounds1) // Prints: (0.0, -7.0, 252.3486328125, 37.0)
print(bounds2) // Prints: (0.0, -7.0, 252.3486328125, 31.0)
Investigation
I compared fonts, font attributes, traits, feature settings, even binary font tables but I didn't find any difference. It seems like there is something inside that alters behaviour.
I found that if I add .usesDeviceMetrics option to NSAttributedString bounds getter, it returns the same result but also completely different frame.
let bounds1 = NSAttributedString(string: "Hello, World! gÖ,|#", attributes: [.font: font1]).boundingRect(with: .infinity, options: [.usesDeviceMetrics])
let bounds2 = NSAttributedString(string: "Hello, World! gÖ,|#", attributes: [.font: font2]).boundingRect(with: .infinity, options: [.usesDeviceMetrics])
print(bounds1) // Prints: (2.3583984375, -6.6357421875, 249.9609375, 33.837890625)
print(bounds2) // Prints: (2.3583984375, -6.6357421875, 249.9609375, 33.837890625)
Question
How to load a font using CoreText in a way that NSFont does?
Minor question: Why does NSTextField set it's frame origin to -2.0?
Just extensions I used in code above:
extension CGSize {
public static var infinity: Self {
.init(width: CGFloat.infinity, height: CGFloat.infinity)
}
}
extension NSTextField {
func setAsLabel() {
translatesAutoresizingMaskIntoConstraints = false
isBezeled = false
isBordered = false
isEditable = false
isSelectable = false
drawsBackground = false
}
}