[TOC]
CTFramesetterSuggestFrameSizeWithConstraints
result does not match with CTFrameDraw
actual drawing result
iOS
Core Text
Incorrect/Unexpected Behavior
iOS 17.1 Seed 3 (21B5066a)
On device
-
Create NSAttributedString:
int fontSize = 16 * [NSScreen.mainScreen backingScaleFactor]; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"《眼中星》" attributes:@{ NSFontAttributeName: [NSFont boldSystemFontOfSize:fontSize], NSForegroundColorAttributeName: [NSColor whiteColor], }];
-
Create CTFramesetter:
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedString);
-
Get suggested size using
CTFramesetterSuggestFrameSizeWithConstraints
:CGSize stringSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), NULL);
-
Create CTFrame:
float width = ceil(stringSize.width); float height = ceil(stringSize.height); CGPathRef path = CGPathCreateWithRect(CGRectMake(0.0, 0.0, width, height), NULL); CTFrameRef ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
-
Get, print and compare the VisibleStringRange and StringRange:
CFRange stringRange = CTFrameGetStringRange(ctFrame); CFRange visibleStringRange = CTFrameGetVisibleStringRange(ctFrame); NSLog(@"stringRange.location: %@ stringRange.length: %@", @(stringRange.location), @(stringRange.length)); NSLog(@"visibleStringRange.location: %@ visibleStringRange.length: %@", @(visibleStringRange.location), @(visibleStringRange.length)); NS NSAssert(stringRange.length == visibleStringRange.length, @"StringRange mismatch!");
On iOS 16.* and below, macOS 13.* and below, the assertion won't be triggered, while devices running iOS 17.* and macOS 14.* does.
This issue could also be observed by drawing the CTFrame on screen. The expected output should be "《眼中星》", but the actual drawing result shows "《眼中".
The above code is disassembled from SpriteKit.framework::SKCLabelNode::rebuildText
and simplified as a demo to explain the issue, which means a simple cross-platform game application using SpriteKit will also reproduce it. There is also a simple game application project to reproduce the issue in the appendix and Github repo.
It seems that the UIKit does not rely on CTFramesetterSuggestFrameSizeWithConstraints
but the NSStringDrawingEngine
to get the suggest size and draw the final frame since the Symbolic Breakpoint won't be triggered when using UILabel
in normal applications, so the issue can't be reproduced.