Search code examples
macosnsattributedstringnstextviewnsmutableattributedstringcatextlayer

Different font output for same NSAttributedString with CATextLayer and NSTextView


In macOS using the same NSMutableAttributedString for a CATextLayer and a NSTextView seems to give different rendering results. The NSTextView has a slightly larger font than a CATextLayer. This behavior seems to occur with any type of font structure (NSFont, CTFontCreateWithName, etc) fed to the controls. Even not defining a font will cause this to happen when the controls default to the system font. Here's a distilled down snippet that will run as a Playground. It creates a CATextLayer on the left side and a NSTextView on the right side. Uses the exact same font and string. Anyone solved this one yet?

enter image description here

import Cocoa

let attributes = [NSAttributedString.Key.font: NSFont(name: "Helvetica", size: 23.0)!,
                  NSAttributedString.Key.foregroundColor:   NSColor.gray]

let theString = NSMutableAttributedString(string: "The Quick Brown Fox Jumped Over The Lazy Dogs Back", attributes: attributes)

var parentView = NSView(frame: NSRect(origin: NSPoint(x: 0, y: 0), size: CGSize(width: 300, height: 300)))

// create CATextLayer - left side
var textLayer = CATextLayer()
textLayer.isWrapped = true
textLayer.contentsScale = NSScreen.main!.backingScaleFactor
textLayer.backgroundColor = CGColor.white
textLayer.foregroundColor = CGColor.black
textLayer.string = theString

var layerView = NSView(frame: NSRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 90, height: 300)))
layerView.wantsLayer = true
layerView.layer = textLayer
parentView.addSubview(layerView)

// create NSTextView - right side
var textView = NSTextView(frame: NSRect(origin: NSPoint(x: 100, y: 0), size: CGSize(width: 90, height: 300)))
textView.textStorage?.setAttributedString(theString as NSAttributedString)
  
parentView.addSubview(textView)

// final display
parentView

Solution

  • The final ruling is - NSTextView and CATextLayer are going to render differently. Here is what Apple says:

    "This is an interesting question and I have visited this before with other developers. Yes, you are correct. The text rendering inside of CATextLayers is different than in an NSTextView. I have talked with engineering about these differences before and I do not think there is a solution to the problem you have posed using CATextLayer. Here is why:

    Attributed strings drawn to a CATextLayer use different attributes than regular attributed strings. These are described here - https://developer.apple.com/documentation/coretext/styling_attributed_strings. There is some pairity with some of the regular attributed string attributes so you'll see some strings will seem to work, but some of the atrributes used by regular attributed strings are not supported. For example, strike-through text is not supported.

    CATextLayer also has a different concept of sizing and spacing than when drawing to regular layers. This is undocumented and I do not know how to reconcile these difference with NSTextView output.

    So, in summary, yes, for the reasons mentioned above, I expect that sometimes whatever you are drawing to CATextLayer will look different than what you are seeing drawn inside of an NSTextView. These differences are not documented and we do not have a forumula to map between text drawn in CATextLayer and NSTextView. I recommend using a layer-backed view instead."