【问题标题】:Core Text's CTFramesetterSuggestFrameSizeWithConstraints() returns incorrect size every timeCore Text 的 CTFramesetterSuggestFrameSizeWithConstraints() 每次都返回不正确的大小
【发布时间】:2011-02-12 01:22:09
【问题描述】:

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

返回的尺寸总是计算出正确的宽度,但高度总是比预期的略短。

这是使用此方法的正确方法吗?

还有其他方式来布局Core Text吗?

似乎我不是唯一一个遇到这种方法问题的人。见https://devforums.apple.com/message/181450

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

核心文本:133.569336 x 16.592285

石英:135.000000 x 31.000000

【问题讨论】:

  • 遇到了同样的问题。如果我要求它计算 3 行,它总是比它应该计算的少一行,它会给我正确的计算 2.. 等等等等。

标签: cocoa cocoa-touch core-text


【解决方案1】:

试试这个..似乎有效:

+(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;
}

【讨论】:

  • 这似乎是我能想象到的唯一可行的解​​决方案。在某些情况下,所有其他方法都失败了。
  • 如果您使用零作为高度,它不会将高度限制为任何高度。我为这个问题写了一个类别,在所有情况下都对我有用。见stackoverflow.com/questions/16586190/…
  • 如果有人感兴趣,这与CTFramesetterSuggestFrameSizeWithConstraints 的性能基本相同,但实际上每次都是正确的
【解决方案2】:

对于单行帧,试试这个:

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 了解更多信息。

【讨论】:

  • 非常感谢,Mo。这帮助我找到了在处理多行标签时似乎可靠的 SuggestSize 解决方法。
  • 很高兴我能帮上忙。你检查过我对wrongHeight 的看法是否正确吗?我想提交错误报告。 PS。谢谢gf。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      复活。

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

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

      更新

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

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

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

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

      【讨论】:

        【解决方案6】:

        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
        

        【讨论】:

          猜你喜欢
          • 2011-03-23
          • 2019-02-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-10-07
          • 2015-08-21
          相关资源
          最近更新 更多