如何获得NSAttributedString的光学范围?

时间:2018-11-13 13:17:54

标签: swift nsattributedstring bounds

我需要属性字符串的光学范围。我知道我可以调用.size()方法并读取其宽度,但这显然给了我印刷范围,并在右边增加了空间。

我的字符串都非常短,仅包含1-3个字符,因此每个字符串都将恰好包含一个glyphrun。

我找到了函数CTRunGetImageBounds,并按照注释中链接的提示进行操作之后,我能够提取行程并获得界限,但是显然,这并没有给我想要的结果。

以下Swift 4代码可在XCode9 Playground中使用:

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,最后测量其宽度。结果显示在实时视图的第二行。

第一行是期望的结果:width函数精确返回每个字母的宽度,从而使它们彼此接触。

我希望属性字符串版本具有相同的结果,但是在这里ImageBounds似乎添加了我想避免的其他填充。

如何测量给定文本从最左边到最右边像素的确切宽度?

是否有一种不太笨拙的方法来实现这一目标而不必强制转换四次(NSAtt.Str-> CTLine-> CTRun-> CGRect-> maxX)?

1 个答案:

答案 0 :(得分:0)

好的,我自己找到了答案:

  1. 使用CTRunGetImageBounds的.width参数代替.maxX会带来正确的结果

  2. CTLine也存在相同的功能:CTLineGetImageBounds