I have an NSTextVIew
in which I am only showing mono-spaced characters from the standard alphabet. So no numbers, special characters, emojis, etc. Each character equals one glyph. On top of the text I need to draw some shapes, and I am looking for a way to access some metrics from the text system:
See the picture for what I mean.
There don't seem to be any properties that I can use directly, or at least I haven't found them, so I am now use the text view's layoutManager
to obtain these values:
For the first one I obtain the enclosing rects for two adjacent characters via the boundingRect(forGlyphRange glyphRange: NSRange, in container: NSTextContainer) -> NSRect
method of the layoutmanager, and subtract the origin.x for both rects.
For the second one, I could use the same function, but then I need to know the range for the first character on the second line. Or iterate over all the characters and once the origin.y of the enclosing rect changes, I have the first character on the second line and I can calculate the distance between two lines.
EDIT : here's possible code using the layoutManager:
typealias TextMetrics = (distanceBetweenCharacters: CGFloat, distanceBetweenLines: CGFloat)
var metrics: TextMetrics = self.textMetrics() // need to update when text changes
func textMetrics() -> TextMetrics {
guard let lm = self.layoutManager,
let tc = self.textContainer
else { return (0,0)
}
var distanceBetweenCharacters: CGFloat = 0.0
var distanceBetweenLines: CGFloat = 0.0
if string.count > 2 {
let firstRect = lm.boundingRect(forGlyphRange: NSRange(location: 0, length: 1), in: tc)
let secondRect = lm.boundingRect(forGlyphRange: NSRange(location: 1, length: 1), in: tc)
distanceBetweenCharacters = secondRect.maxX - firstRect.maxX
for (index, _) in string.enumerated() {
let rect = lm.boundingRect(forGlyphRange: NSRange(location: index, length: 1), in: tc)
if rect.maxY > firstRect.maxY { // reached next line
distanceBetweenLines = rect.maxY - firstRect.maxY
break
}
}
}
return (distanceBetweenCharacters, distanceBetweenLines)
}
I also looked at getting these from the defaultParagraphStyle
, but if I access that, it is nil
.
Is there maybe another, easier way to obtain these values?
After some more searching and trial and error, I found that distanceBetweenLines
could be calculated from the font metrics and the lineHeightMultiple, which is a property from NSParagraphStyle
, but it could also be defined outside the paragraph style, which is what I do.
So in the end this works:
let distanceBetweenLines = layoutManager.defaultLineHeight(for: myTextFont) * lineHeightMultiple
For distanceBetweenCharacters
, I have not found another solution.
EDIT
Based on the suggestion from @Willeke in the comment below, I now calculate distanceBetweenCharacters
as follows:
let distanceBetweenCharacters = myTextFont.advancement(forCGGlyph: layoutManager.cgGlyph(at: 0)).width + myKerning