Core Text 的 CTFramesetterSuggestFrameSizeWithConstraints() 每次都会返回错误的大小
根据文档,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
According to the docs, CTFramesetterSuggestFrameSizeWithConstraints ()
"determines the frame size needed for a string range".
Unfortunately the size returned by this function is never accurate. Here is what I am doing:
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);
The returned size always has the correct width calculated, however the height is always slightly shorter than what is expected.
Is this the correct way to use this method?
Is there any other way to layout Core Text?
Seems I am not the only one to run into problems with this method. See https://devforums.apple.com/message/181450.
Edit:
I measured the same string with Quartz using sizeWithFont:
, supplying the same font to both the attributed string, and to Quartz. Here are the measurements I received:
Core Text: 133.569336 x 16.592285
Quartz: 135.000000 x 31.000000
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
试试这个..似乎有效:
try this.. seem to work:
对于单行框架,请尝试以下操作:
对于多行框架,您还需要添加行的引线(请参阅 核心文本编程指南)
出于某种原因,
CTFramesetterSuggestFrameSizeWithConstraints()
正在使用上升和下降的差异来计算高度:这可能是一个错误?
我在框架宽度方面还遇到一些其他问题;它值得检查,因为它只在特殊情况下显示。有关详细信息,请参阅此问题。
For a single line frame, try this:
For multiline frames, you also need to add the line's lead (see a sample code in Core Text Programming Guide)
For some reason,
CTFramesetterSuggestFrameSizeWithConstraints()
is using the difference in ascent and descent to calculate the height:It could be a bug?
I'm having some other problems with the width of the frame; It's worth checking out as it only shows in special cases. See this question for more.
问题是您必须在测量文本之前对其应用段落样式。如果不这样做,那么您将获得默认的前导 0.0。我在此处对此问题的重复答案中提供了如何执行此操作的代码示例 https://stackoverflow.com/a/ 10019378/1313863。
The problem is that you have to apply a paragraph style to the text before you measure it. If you don't then you get the default leading of 0.0. I provided a code sample for how to do this in my answer to a duplicate of this question here https://stackoverflow.com/a/10019378/1313863.
ing.conti 的答案,但在 Swift 4 中:
我确实尝试将其与 Objective C 代码保持为 1:1,但 Swift 在处理指针时不太好,因此需要进行一些更改来进行转换。
我还做了一些基准测试,将此代码(及其 ObjC 对应项)与另一个高度方法进行比较。需要注意的是,我使用了一个巨大且非常复杂的属性字符串作为输入,并且还在 sim 上进行了操作,因此时间本身毫无意义,但相对速度是正确的。
ing.conti's answer but in Swift 4:
I did try and keep it as 1:1 with the Objective C code but Swift is not as nice when handling pointers so some changes were required for casting.
I also did some benchmarks comparing this code (and it's ObjC counterpart) to another height methods. As a heads up, I used a HUGE and very complex attributed string as input and also did it on the sim so the times themselves are meaningless however the relative speeds are correct.
这可能看起来很奇怪,但我发现如果你先使用 ceil 函数,然后在高度上加 +1 ,它总是有效的。许多第三方 API 都使用这个技巧。
It might seem strange but I found that if you use
ceil
function first and then add +1 to the height it will always work. Many third party APIs use this trick.复活。
当最初确定线条应放置在框架内的位置时,核心文本似乎会为了线条原点计算而调整上升+下降。特别是,似乎在上升中添加了 0.2*(ascent+descent),然后下降和合成上升都被
floor(x + 0.5)
修改,然后基线位置是根据这些调整后的上升和下降计算。这两个步骤都受到某些条件的影响,我不确定这些条件的性质,而且我也已经忘记了在哪一点考虑段落样式,尽管几天前才研究过。我已经放弃只考虑一条线从其基线开始,而不是试图找出实际线的着陆点。不幸的是,这似乎仍然不够:段落样式没有反映在
CTLineGetTypgraphicBounds()
中,并且像 Klee 这样具有非零前导的字体最终会穿过路径矩形!不知道该怎么办......可能是另一个问题。更新
似乎
CTLineGetBoundsWithOptions(line, 0)
确实获得了正确的行边界,但并不完全:行之间存在间隙,并且使用某些字体(再次是 Klee)间隙为负且线条重叠...不知道该怎么办。 :|至少我们更接近一点?即使如此,它仍然没有考虑段落样式>:|
CTLineGetBoundsWithOptions()
未在 Apple 的文档网站上列出,可能是由于当前版本的文档生成器中存在错误。然而,它是一个完整记录的 API — 您可以在头文件中找到它,并且在 WWDC 2012 会议 226 上详细讨论了它。没有一个选项与我们相关:它们通过采用某些字体设计选择来减少边界矩形考虑在内(或者在新的 kCTLineBoundsIncludeLanguageExtents 的情况下随机增加边界矩形)。不过,一般来说,一个有用的选项是 kCTLineBoundsUseGlyphPathBounds,它相当于 CTLineGetImageBounds(),但不需要指定 CGContext(因此不需要受现有文本矩阵或 CTM 的约束)。
Resurrecting.
When initially determining where lines should be placed within a frame, Core Text seems to massage the ascent+descent for the purposes of line origin calculation. In particular, it seems like 0.2*(ascent+descent) is added to the ascent, and then both the descent and resultant ascent are modified by
floor(x + 0.5)
, and then the baseline positions are calculated based on these adjusted ascents and descents. Both of these steps are affected by certain conditions whose nature I am not sure, and I also already forgot at which point paragraph styles are taken into account, despite only looking into it a few days ago.I've already resigned to just considering a line to start at its baseline and not trying to figure out what the actual lines land at. Unfortunately, this still does not seem to be enough: paragraph styles are not reflected in
CTLineGetTypographicBounds()
, and some fonts like Klee that have nonzero leadings wind up crossing the path rect! Not sure what to do about this... probably for another question.UPDATE
It seems
CTLineGetBoundsWithOptions(line, 0)
does get the proper line bounds, but not quite fully: there's a gap between lines, and with some fonts (Klee again) the gap is negative and the lines overlap... Not sure what to do about this. :| At least we're slightly closer??And even then it still does not take paragraph styles into consideration >:|
CTLineGetBoundsWithOptions()
is not listed on Apple's documentation site, possibly due to a bug in the current version of their documentation generator. It is a fully documented API, however — you'll find it in the header files and it was discussed at length at WWDC 2012 session 226.None of the options are relevant to us: they reduce the bounds rect by taking certain font design choices into consideration (or increase the bounds rect randomly, in the case of the new
kCTLineBoundsIncludeLanguageExtents
). One useful option in general, though, iskCTLineBoundsUseGlyphPathBounds
, which is equivalent toCTLineGetImageBounds()
but without needing to specify aCGContext
(and thus without being subject to an existing text matrix or CTM).经过几周的尝试,尝试了一切可能的组合,我取得了突破并找到了有效的方法。这个问题在 macOS 上似乎比在 iOS 上更为突出,但在两者上仍然存在。
对我有用的是使用
CATextLayer
而不是NSTextField
(在 macOS 上)或UILabel
(在 iOS 上)。并使用 boundingRect(with:options:context:) 而不是 <代码>CTFramesetterSuggestFrameSizeWithConstraints。尽管从理论上讲,后者应该比前者更低级别,而且我假设会更精确,但游戏规则的改变者是 NSString.DrawingOptions.usesDeviceMetrics。
建议的镜框尺寸非常合适。
示例:
然后您可以将
CATextLayer
添加到任何NSView
/UIView
。苹果
系统
After weeks of trying everything, any combination possible, I made a break through and found something that works. This issue seems to be more prominent on macOS than on iOS, but still appears on both.
What worked for me was to use a
CATextLayer
instead of aNSTextField
(on macOS) or aUILabel
(on iOS).And using boundingRect(with:options:context:) instead of
CTFramesetterSuggestFrameSizeWithConstraints
. Even though in theory the latter should be more lower level than the former, and I was assuming would be more precise, the game changer turns out to be NSString.DrawingOptions.usesDeviceMetrics.The frame size suggested fits like a charm.
Example:
Then you can add the
CATextLayer
to anyNSView
/UIView
.macOS
iOS