如何获取文本中每个字形的真实位置?CTFont相关技术疑问
解决Core Text中获取字形真实位置的问题
这个问题其实是因为你忽略了Core Text排版中的**字距调整(Kerning)**和潜在的连字处理——CTFontGetAdvancesForGlyphs返回的是单个字形的「孤立宽度」,而实际排版时相邻字形之间会自动调整间距(比如"To"里的T和o会靠得更近),所以直接累加自然会比真实文本长度大。要获取每个字形的真实排版位置,得利用Core Text的排版布局对象来处理这些自动调整,而不是手动计算。
正确的实现步骤
核心思路是通过CTLine和CTRun来获取经过排版调整后的字形位置和宽度,这两个类会自动处理字距、连字等排版细节:
- 先创建带字体属性的属性字符串,明确排版规则
- 生成
CTLineRef排版行对象,让Core Text完成排版计算 - 遍历行内的
CTRunRef(相同字体属性的字形组),提取每个字形的真实位置和调整后宽度
代码示例(Objective-C)
// 假设你已经有目标字符串和字体 NSString *targetString = @"ToTeTest"; UIFont *font = [UIFont systemFontOfSize:16.0]; // 转换为Core Text兼容的类型 CFStringRef ctString = (__bridge CFStringRef)targetString; CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); // 创建带字体属性的可变属性字符串 CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), ctString); CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFStringGetLength(ctString)), kCTFontAttributeName, ctFont); // 生成排版行 CTLineRef line = CTLineCreateWithAttributedString(attrString); // 获取行内的所有CTRun(相同字体属性的字形组) CFArrayRef runs = CTLineGetGlyphRuns(line); CFIndex runCount = CFArrayGetCount(runs); for (CFIndex i = 0; i < runCount; i++) { CTRunRef run = CFArrayGetValueAtIndex(runs, i); CFIndex glyphCount = CTRunGetGlyphCount(run); // 获取每个字形的真实起始位置(已包含字距调整) const CGPoint *glyphPositions = CTRunGetPositionsPtr(run); // 获取每个字形调整后的推进宽度 const CGSize *glyphAdvances = CTRunGetAdvancesPtr(run); // 可选:获取字形对应的原字符串字符索引(处理连字时有用) const CFIndex *stringIndices = CTRunGetStringIndicesPtr(run); for (CFIndex j = 0; j < glyphCount; j++) { CGPoint position = glyphPositions[j]; CGSize advance = glyphAdvances[j]; CFIndex charIndex = stringIndices[j]; NSLog(@"原字符串索引%d的字形:起始X=%.2f,调整后宽度=%.2f", charIndex, position.x, advance.width); // 字形的结束位置 = position.x + advance.width } } // 释放Core Text对象,避免内存泄漏 CFRelease(line); CFRelease(attrString); CFRelease(ctFont);
关键细节解释
CTRunGetPositionsPtr返回的CGPoint数组中,x值是该字形在排版行中的真实起始横坐标(已经包含了字距调整),y值对应基线纵坐标(单行排版时可忽略)。CTRunGetAdvancesPtr返回的宽度是经过调整后的推进宽度,累加这些宽度会和CTLineGetTypographicBounds返回的总行宽度完全一致。- 如果遇到连字(比如"fi"被合并成一个字形),
glyphCount会小于原字符串的字符数,这时候stringIndices可以帮你映射回原字符串的字符位置。
内容的提问来源于stack exchange,提问作者aleksid1
相关产品推荐
相关产品推荐

