根据文档,CTFramesetterSuggestFrameSizeWithConstraints ()
“确定字符串范围所需的帧大小”。
不幸的是,此函数返回的大小永远不准确。这就是我在做的事情:
NSAttributedString *string = [[[NSAttributedString alloc] initWithString:@"lorem ipsum" attributes:nil] autorelease];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) string);
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, CGSizeMake(rect.size.width, CGFLOAT_MAX), NULL);
返回的尺寸始终具有正确的宽度计算值,但高度始终略短于预期值。
这是使用此方法的正确方法吗?
有没有其他方法可以布局核心文本?
似乎我不是唯一遇到此方法问题的人。请参阅https://devforums.apple.com/message/181450。
编辑:
我使用sizeWithFont:
使用Quartz测量相同的字符串,为属性字符串和Quartz提供相同的字体。以下是我收到的测量结果:
核心文字:133.569336 x 16.592285
石英:135.000000 x 31.000000
答案 0 :(得分:14)
试试这个..似乎有效:
+(CGFloat)heightForAttributedString:(NSAttributedString *)attrString forWidth:(CGFloat)inWidth
{
CGFloat H = 0;
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString( (CFMutableAttributedStringRef) attrString);
CGRect box = CGRectMake(0,0, inWidth, CGFLOAT_MAX);
CFIndex startIndex = 0;
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, box);
// Create a frame for this column and draw it.
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(startIndex, 0), path, NULL);
// Start the next frame at the first character not visible in this frame.
//CFRange frameRange = CTFrameGetVisibleStringRange(frame);
//startIndex += frameRange.length;
CFArrayRef lineArray = CTFrameGetLines(frame);
CFIndex j = 0, lineCount = CFArrayGetCount(lineArray);
CGFloat h, ascent, descent, leading;
for (j=0; j < lineCount; j++)
{
CTLineRef currentLine = (CTLineRef)CFArrayGetValueAtIndex(lineArray, j);
CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading);
h = ascent + descent + leading;
NSLog(@"%f", h);
H+=h;
}
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
return H;
}
答案 1 :(得分:5)
对于单行框架,请尝试以下方法:
line = CTLineCreateWithAttributedString((CFAttributedStringRef) string);
CGFloat ascent;
CGFloat descent;
CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
CGFloat height = ascent+descent;
CGSize textSize = CGSizeMake(width,height);
对于多线框架,您还需要添加线条的线索(请参阅Core Text Programming Guide中的示例代码)
出于某种原因,CTFramesetterSuggestFrameSizeWithConstraints()
正在使用上升和下降的差异来计算高度:
CGFloat wrongHeight = ascent-descent;
CGSize textSize = CGSizeMake(width, wrongHeight);
这可能是一个错误?
我的框架宽度还有其他一些问题;它值得一试,因为它只在特殊情况下显示。有关详情,请参阅this question。
答案 2 :(得分:3)
问题是在测量之前必须在文本中应用段落样式。如果不这样做,则默认前导值为0.0。我在这里https://stackoverflow.com/a/10019378/1313863回答了这个问题的副本,提供了一个如何执行此操作的代码示例。
答案 3 :(得分:0)
这可能看起来很奇怪,但我发现如果你首先使用ceil
功能,然后在高度上添加+1,它将始终有效。许多第三方API都使用这个技巧。
答案 4 :(得分:0)
复活。
当最初确定在框架内放置线条的位置时,Core Text似乎按下上升+下降以进行线原点计算。特别是,似乎0.2 *(上升+下降)被添加到上升,然后下降和结果上升被floor(x + 0.5)
修改,然后基线位置基于这些调整的上升和下降来计算。这两个步骤都受到某些条件的影响,这些条件的性质我不确定,而且我也已经忘记了段落样式被考虑在哪一点,尽管几天前只是查看它。
我已经辞职,只考虑一条线从其基线开始而不是试图弄清楚实际的线路是什么。不幸的是,这似乎还不够:段落样式不会反映在CTLineGetTypographicBounds()
中,而像Klee这样的非零引导字体会越过路径矩形!不知道该怎么做...可能是另一个问题。
<强>更新强>
似乎CTLineGetBoundsWithOptions(line, 0)
确实获得了正确的线条界限,但并不完全:线条之间存在间隙,而且有些字体(Klee再次)差距为负且线条重叠......不确定是什么要这样做。 :|至少我们稍微靠近了?
即使这样,它仍然不考虑段落样式&gt;:|
Apple的文档站点上未列出 CTLineGetBoundsWithOptions()
,可能是由于当前版本的文档生成器中存在错误。它是一个完整记录的API,但是 - 您可以在头文件中找到它,并在WWDC 2012会话226中进行了详细讨论。
没有一个选项与我们相关:它们通过考虑某些字体设计选择来减少边界矩形(或者在新kCTLineBoundsIncludeLanguageExtents
的情况下随机增加边界矩形)。但是,一般来说,一个有用的选项是kCTLineBoundsUseGlyphPathBounds
,它等同于CTLineGetImageBounds()
但不需要指定CGContext
(因此不受现有文本矩阵或CTM的约束)。
答案 5 :(得分:0)
ing.conti的答案,但是在Swift 4中:
var H:CGFloat = 0
// Create the framesetter with the attributed string.
let framesetter = CTFramesetterCreateWithAttributedString(attributedString as! CFMutableAttributedString)
let box:CGRect = CGRect.init(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)
let startIndex:CFIndex = 0
let path:CGMutablePath = CGMutablePath()
path.addRect(box)
// Create a frame for this column and draw it.
let frame:CTFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(startIndex, 0), path, nil)
// Start the next frame at the first character not visible in this frame.
//CFRange frameRange = CTFrameGetVisibleStringRange(frame);
//startIndex += frameRange.length;
let lineArray:CFArray = CTFrameGetLines(frame)
let lineCount:CFIndex = CFArrayGetCount(lineArray)
var h:CGFloat = 0
var ascent:CGFloat = 0
var descent:CGFloat = 0
var leading:CGFloat = 0
for j in 0..<lineCount {
let currentLine = unsafeBitCast(CFArrayGetValueAtIndex(lineArray, j), to: CTLine.self)
CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading)
h = ascent + descent + leading;
H+=h;
}
return H;
我确实尝试过使用Objective C代码将其保持为1:1,但是Swift在处理指针时效果不佳,因此需要进行一些更改才能进行投射。
我也做了一些基准测试,比较了此代码(与ObjC对应)与另一种高度方法。请注意,我使用了巨大且非常复杂的属性字符串作为输入,并且也在SIM卡上进行了输入,因此时间本身毫无意义,但是相对速度是正确的。
Runtime for 1000 iterations (ms) BoundsForRect: 8909.763097763062
Runtime for 1000 iterations (ms) layoutManager: 7727.7010679244995
Runtime for 1000 iterations (ms) CTFramesetterSuggestFrameSizeWithConstraints: 1968.9229726791382
Runtime for 1000 iterations (ms) CTFramesetterCreateFrame ObjC: 1941.6030206680298
Runtime for 1000 iterations (ms) CTFramesetterCreateFrame-Swift: 1912.694974899292