Мне нужны оптические границы приписанной строки. Я знаю, что могу вызвать метод .size () и прочитать его ширину, но это, очевидно, дает мне типографские границы с дополнительным пространством справа.
Все мои строки будут очень короткими и состоят только из 1-3 символов, поэтому каждая строка будет содержать ровно один глифрун.
Я нашел функцию CTRunGetImageBounds, и, следуя подсказкам в ссылке из комментария, я смог извлечь прогон и получить границы, но, очевидно, это не дает мне желаемого результата.
Следующий код swift 4 работает на игровой площадке XCode9:
import Cocoa
import PlaygroundSupport
public func getGlyphWidth(glyph: CGGlyph, font: CTFont) -> CGFloat {
var glyph = glyph
var bBox = CGRect()
CTFontGetBoundingRectsForGlyphs(font, .default, &glyph, &bBox, 1)
return bBox.width
}
class MyView: NSView {
init(inFrame: CGRect) {
super.init(frame: inFrame)
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
// setup context properties
let context: CGContext = NSGraphicsContext.current!.cgContext
context.setStrokeColor(CGColor.black)
context.setTextDrawingMode(.fill)
// prepare variables and constants
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L"]
let font = CTFontCreateWithName("Helvetica" as CFString, 48, nil)
var glyphX: CGFloat = 10
// draw alphabet as single glyphs
for letter in alphabet {
var glyph = CTFontGetGlyphWithName(font, letter as CFString)
var glyphPosition = CGPoint(x: glyphX, y: 80)
CTFontDrawGlyphs(font, &glyph, &glyphPosition, 1, context)
glyphX+=getGlyphWidth(glyph: glyph, font: font)
}
let textStringAttributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.font : font,
]
glyphX = 10
// draw alphabet as attributed strings
for letter in alphabet {
let textPosition = NSPoint(x: glyphX, y: 20)
let text = NSAttributedString(string: letter, attributes: textStringAttributes)
let line = CTLineCreateWithAttributedString(text)
let runs = CTLineGetGlyphRuns(line) as! [CTRun]
let width = (CTRunGetImageBounds(runs[0], nil, CFRange(location: 0,length: 0))).maxX
text.draw(at: textPosition)
glyphX += width
}
}
}
var frameRect = CGRect(x: 0, y: 0, width: 400, height: 150)
PlaygroundPage.current.liveView = MyView(inFrame: frameRect)
Код рисует отдельные буквы от A - L в виде одиночных глифов в верхнем ряду живого вида игровой площадки. Горизонтальная позиция будет сдвигаться после каждой буквы на ширину буквы, полученную с помощью функции getGlyphWidth.
Затем он использует те же буквы для создания из него приписанных строк, которые затем будут использованы для создания сначала CTLine, извлечения (только) CTRun из него и, наконец, измерения его ширины. Результат виден во второй строке в режиме реального времени.
Первая строка - желаемый результат: функция ширины возвращает ширину каждой буквы, в результате чего они соприкасаются друг с другом.
Я хочу получить тот же результат с атрибутивной версией строки, но здесь ImageBounds добавляет дополнительный отступ, которого я хочу избежать.
Как измерить точную ширину от крайнего левого до правого пикселя данного текста?
И есть ли менее неуклюжий способ добиться этого без четырехкратного приведения (NSAtt.Str-> CTLine-> CTRun-> CGRect-> maxX)?