Core Text的CTFramesetterSuggestFrameSizeWithConstraints()每次都返回不正确的大小

时间:2010-04-25 09:02:15

标签: cocoa cocoa-touch core-text

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

6 个答案:

答案 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