10

根据文档,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

编辑:我使用 Quartz 测量了相同的字符串sizeWithFont:,为属性字符串和 Quartz 提供相同的字体。以下是我收到的测量结果:

核心文本:133.569336 x 16.592285

石英:135.000000 x 31.000000

4

6 回答 6

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;
}
于 2010-11-18T12:58:12.763 回答
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);

这可能是一个错误?

我在框架的宽度方面遇到了一些其他问题;值得一试,因为它只在特殊情况下显示。有关更多信息,请参阅此问题

于 2010-05-05T02:45:02.663 回答
3

问题是您必须在测量文本之前将段落样式应用于文本。如果不这样做,那么您将获得 0.0 的默认前导。我在此处https://stackoverflow.com/a/10019378/1313863对此问题的重复回答中提供了如何执行此操作的代码示例。

于 2012-04-04T21:21:13.413 回答
0

这可能看起来很奇怪,但我发现如果你ceil先使用函数,然后将 +1 添加到高度,它总是可以工作的。许多第三方 API 使用此技巧。

于 2011-09-04T08:04:14.160 回答
0

复活。

当最初确定线应该放置在框架内的什么位置时,Core Text 似乎为了线原点计算而按摩了上升+下降。特别是,似乎 0.2*(ascent+descent) 被添加到上升,然后下降和合成上升都被修改floor(x + 0.5),然后根据这些调整的上升和下降计算基线位置。这两个步骤都受到某些条件的影响,我不确定这些条件的性质,而且我也已经忘记了在什么时候考虑段落样式,尽管几天前才研究过。

我已经辞职只考虑一条线从它的基线开始,而不是试图弄清楚实际线的位置。不幸的是,这似乎还不够:段落样式没有反映在 中CTLineGetTypographicBounds(),并且像 Klee 这样具有非零前导的字体最终会穿过路径 rect! 不知道该怎么做......可能是另一个问题。

更新

似乎CTLineGetBoundsWithOptions(line, 0)确实得到了正确的线条界限,但并不完全:线条之间存在间隙,并且对于某些字体(又是 Klee),间隙是负数并且线条重叠......不知道该怎么做。:| 至少我们稍微接近一点??

即便如此,它仍然没有考虑段落样式>:|

CTLineGetBoundsWithOptions()未在 Apple 的文档站点上列出,可能是由于其文档生成器的当前版本中存在错误。但是,它是一个完整记录的 API——您可以在头文件中找到它,并且在 WWDC 2012 session 226 上进行了详细讨论。

这些选项都与我们无关:它们通过考虑某些字体设计选择来减少边界矩形(或者在 new 的情况下随机增加边界矩形kCTLineBoundsIncludeLanguageExtents)。但是,一般来说,一个有用的选项是kCTLineBoundsUseGlyphPathBounds,它等效于CTLineGetImageBounds()但不需要指定 a CGContext(因此不受现有文本矩阵或 CTM 的约束)。

于 2017-01-07T18:17:57.743 回答
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
于 2018-08-21T18:18:25.987 回答