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
}
}
After months I found the reason. There is difference in behaviour if app is compiled for deployment target macOS 10.13 and macOS 10.14+. Seems like there is a bug in macOS 10.13 and loading font is strange. I compared NSTextField
with 2 fonts loaded as NSFont
and CTFont
.
(Again. Does not depend on OS where app runs but what deployment target it was compiled)
I also have custom implementation of label that uses TextKit objects NSTextLayoutManager
, NSTextContainer
and NSTextStorage
. Text is rendered using layout manager drawGlyphs(forGlyphRange:at:)
and geometry is also provided by these objects.
Deployment target macOS 10.13
Different handling NSFont
and CTFont
by NSTextField
and by NSTextLayoutManager
. All results are different. CTFont
seems to be the closest one in comparison to new OS.
Deployment target macOS 10.14
All approaches give the same result