使用 Swift 获取字体字形指标

Luc*_*uca 0 glyph fontmetrics textkit swift

为了在自定义 UIView 中定位/缩放字体字形,我需要知道一些字形指标,例如: - 上升(字形从基线的高度,例如“g”位于基线之上的部分) - 下降(字形距基线的深度,例如“g”位于基线下方的部分) - 宽度 - 字距调整 - 斜体校正(字形中超过其斜体宽度的部分)

我尝试子类化NSLayoutManager并从drawGlyphs以下位置读取这些信息:

override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
    enumerateLineFragments(forGlyphRange: glyphsToShow) {
        (rect, usedRect, textContainer, glyphRange, stop) in
        for i in glyphsToShow.location ..< NSMaxRange(glyphsToShow) {

            if let textContainer = self.textContainer(forGlyphAt: glyphsToShow.location, effectiveRange: nil) {
                var glyphRect = self.boundingRect(forGlyphRange: NSMakeRange(i, 1), in:textContainer)
                glyphRect.origin.x += origin.x;
                glyphRect.origin.y += origin.y;

                /// NOW I HAVE AT LEAST THE BOUNDING BOX
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

glyphRect每个字形的宽度/高度完全相同,因此它带有整个字体的最大(高度+深度)垂直空间和最大宽度,这不是我需要的(我需要每个字形的这些信息:是比i高,j有深度,而E没有)。

是否可以通过 TextKit 收集这些信息?其他字体指标(字距调整、斜体校正)是否可用?

谢谢你的帮助,卢卡。

Rob*_*ier 6

通常你会用 Core Text 来做到这一点,而不是 NSLayoutManager,至少对于宽度、上升和体面。我将在下面讨论其他人。

考虑一个属性字符串:

let string = NSAttributedString(string: "squids")
Run Code Online (Sandbox Code Playgroud)

从那里我们想把它分解成“字形运行”。字形运行是具有所有相同属性的字形序列。(在这种情况下只有一个。)为此,首先创建一个 CTLine,然后请求 CTRun 对象:

let line = CTLineCreateWithAttributedString(string)
let glyphRuns = CTLineGetGlyphRuns(line) as! [CTRun]
Run Code Online (Sandbox Code Playgroud)

每次运行都会有一个字体,这是我们查找指标所需的字体,以及一组字形。这是调用代码的草图:

for run in glyphRuns {
    let font = run.font!
    let glyphs = run.glyphs()
    let boundingRects = run.boundingRects(for: glyphs, in: font)
    for pair in zip(glyphs, boundingRects) { print(pair) }
}
Run Code Online (Sandbox Code Playgroud)

当然 CTRun 没有这么好的界面,所以我们需要把它构建为一个扩展:

extension CTRun {
    var font: CTFont? {
        let attributes = CTRunGetAttributes(self) as! [CFString: Any]
        guard let font = attributes[kCTFontAttributeName] else { return nil }
        return (font as! CTFont)
    }

    func glyphs(in range: Range<Int> = 0..<0) -> [CGGlyph] {
        let count = range.isEmpty ? CTRunGetGlyphCount(self) : range.count
        var glyphs = Array(repeating: CGGlyph(), count: count)
        CTRunGetGlyphs(self, CFRangeMake(range.startIndex, range.count), &glyphs)
        return glyphs
    }

    func boundingRects(for glyphs: [CGGlyph], in font: CTFont) -> [CGRect] {
        var boundingRects = Array(repeating: CGRect(), count: glyphs.count)
        CTFontGetBoundingRectsForGlyphs(font, .default, glyphs, &boundingRects, glyphs.count)
        return boundingRects
    }
}
Run Code Online (Sandbox Code Playgroud)

请记住,这些是指标。它们不是绘制字形的实际边界框。一些字体在它们的框外绘制(Zapfino 以它而闻名)。如果你想要实际的图像框,那么你需要CTRunGetImageBounds代替。还有CTFontGetOpticalBoundsForGlyphs一些可以让你的盒子更有用地正确排列(因为如果以与绘制方式不完全匹配的方式排列,字形通常看起来更好)。

我假设您对所有这些都很熟悉,但为了完整起见,请记住,许多本身没有“下降”的东西仍然有下降。例如,在 Helvetica 中,“s”略低于基线(还有“d”和许多其他带有弯曲底的字形)。

对于您注意到的其他指标,其中一些不是字形指标。例如,单个字形没有自己的字距调整指标。字距调整是应用于字形对的东西。

同样,我真的不觉得斜体更正在这里适用。在绝大多数情况下,您不会在 Cocoa 平台上“倾斜”字体。您选择字体的斜体变体,但这是一种完全不同的字体。因此,您不会对“斜体 Helvetica”应用间距校正。您只需替换 Helvetica-Oblique,它有自己的宽度等。(Cocoa 世界中没有 Helvetica-Italic,但有 HelveticaNeue-Italic。)我不知道 Cocoa 布局系统中的任何地方都有“斜体更正”。有些地方可能会很好,但我想不出它实际发生的地方。