使用 Swift 获取字体字形指标
Get font glyph metrics with Swift
为了在自定义 UIView 中定位/缩放字体字形,我需要知道一些字形指标,例如:
- ascent(字形距基线的高度,例如 "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
}
}
}
}
但是glyphRect
对于每个字形都有相同的width/height,所以它承载了整个字体的最大(高度+深度)垂直space和最大宽度,这是这不是我需要的(我需要 every 字形的那些信息:I 比 i 高并且j 有深度而 E 没有)。
是否可以通过 TextKit 收集这些信息?其他字体规格(字距调整、斜体校正)是否可用?
感谢您的帮助,
卢卡.
通常您会使用 Core Text 来执行此操作,而不是 NSLayoutManager,至少对于宽度、上升和体面而言。我将在下面讨论其他人。
考虑一个属性字符串:
let string = NSAttributedString(string: "squids")
从那里我们想把它分解成 "glyph runs." 字形 运行 是具有所有相同属性的字形序列。 (在这种情况下只有一个。)为此,首先创建一个 CTLine,然后请求 CTRun 对象:
let line = CTLineCreateWithAttributedString(string)
let glyphRuns = CTLineGetGlyphRuns(line) as! [CTRun]
每个 运行 都会有一个字体,这是我们查找指标所需要的,以及一个字形集合。这是调用代码的草图:
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) }
}
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
}
}
请记住,这些是 指标。它们不是绘制字形的实际边界框。一些字体绘制在它们的框外(Zapfino 以它而闻名)。如果您想要实际的图像框,则需要 CTRunGetImageBounds
。还有 CTFontGetOpticalBoundsForGlyphs
可以为您提供更有用的框来正确排列事物(因为如果以与绘制方式不完全匹配的方式排列,字形通常看起来会更好)。
我假设您对这一切都很熟悉,但为了完整起见,请记住许多本身没有 "descender" 的事物仍然有血统。例如,在 Helvetica 中,"s" 稍微下降到基线以下(还有 "d" 和许多其他具有弯曲底部的字形)。
对于您注意到的其他指标,其中一些不是字形指标。例如,单个字形没有自己的字距调整指标。字距调整是应用于字形对的东西。
同样,我真的不觉得此处适用斜体校正。在绝大多数情况下,您不会 "italic" Cocoa 平台上的字体。您选择字体的斜体变体,但这是完全不同的字体。因此,您不对 "italicized Helvetica." 应用间距校正您只需替换 Helvetica-Oblique,它有自己的宽度等。(Cocoa 世界中没有 Helvetica-Italic,但有一个 HelveticaNeue-斜体。)我不知道 Cocoa 布局系统中应用了 "italic correction" 的任何地方。有些地方可能会很好,但我想不出它实际发生的地方。
为了在自定义 UIView 中定位/缩放字体字形,我需要知道一些字形指标,例如: - ascent(字形距基线的高度,例如 "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
}
}
}
}
但是glyphRect
对于每个字形都有相同的width/height,所以它承载了整个字体的最大(高度+深度)垂直space和最大宽度,这是这不是我需要的(我需要 every 字形的那些信息:I 比 i 高并且j 有深度而 E 没有)。
是否可以通过 TextKit 收集这些信息?其他字体规格(字距调整、斜体校正)是否可用?
感谢您的帮助, 卢卡.
通常您会使用 Core Text 来执行此操作,而不是 NSLayoutManager,至少对于宽度、上升和体面而言。我将在下面讨论其他人。
考虑一个属性字符串:
let string = NSAttributedString(string: "squids")
从那里我们想把它分解成 "glyph runs." 字形 运行 是具有所有相同属性的字形序列。 (在这种情况下只有一个。)为此,首先创建一个 CTLine,然后请求 CTRun 对象:
let line = CTLineCreateWithAttributedString(string)
let glyphRuns = CTLineGetGlyphRuns(line) as! [CTRun]
每个 运行 都会有一个字体,这是我们查找指标所需要的,以及一个字形集合。这是调用代码的草图:
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) }
}
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
}
}
请记住,这些是 指标。它们不是绘制字形的实际边界框。一些字体绘制在它们的框外(Zapfino 以它而闻名)。如果您想要实际的图像框,则需要 CTRunGetImageBounds
。还有 CTFontGetOpticalBoundsForGlyphs
可以为您提供更有用的框来正确排列事物(因为如果以与绘制方式不完全匹配的方式排列,字形通常看起来会更好)。
我假设您对这一切都很熟悉,但为了完整起见,请记住许多本身没有 "descender" 的事物仍然有血统。例如,在 Helvetica 中,"s" 稍微下降到基线以下(还有 "d" 和许多其他具有弯曲底部的字形)。
对于您注意到的其他指标,其中一些不是字形指标。例如,单个字形没有自己的字距调整指标。字距调整是应用于字形对的东西。
同样,我真的不觉得此处适用斜体校正。在绝大多数情况下,您不会 "italic" Cocoa 平台上的字体。您选择字体的斜体变体,但这是完全不同的字体。因此,您不对 "italicized Helvetica." 应用间距校正您只需替换 Helvetica-Oblique,它有自己的宽度等。(Cocoa 世界中没有 Helvetica-Italic,但有一个 HelveticaNeue-斜体。)我不知道 Cocoa 布局系统中应用了 "italic correction" 的任何地方。有些地方可能会很好,但我想不出它实际发生的地方。