Как я могу получить оптические границы NSAttributedString? - PullRequest
0 голосов
/ 13 ноября 2018

Мне нужны оптические границы приписанной строки. Я знаю, что могу вызвать метод .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)?

1 Ответ

0 голосов
/ 13 ноября 2018

Хорошо, я нашел ответ сам:

  1. Использование параметра .width CTRunGetImageBounds вместо .maxX дает правильный результат

  2. Та же функция также существует для CTLine: CTLineGetImageBounds

...