macos Core Text 中的行距是如何工作的?(为什么它与 NSLayoutManager 不同?)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5511830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How does line spacing work in Core Text? (and why is it different from NSLayoutManager?)
提问by Steven Vandeweghe
I'm trying to draw text using Core Text functions, with a line spacing that's as close as possible to what it would be if I used NSTextView.
我正在尝试使用 Core Text 函数绘制文本,行距尽可能接近使用 NSTextView 时的行距。
Take this font as an example:
以这个字体为例:
NSFont *font = [NSFont fontWithName:@"Times New Roman" size:96.0];
The line height of this font, if I would use it in an NSTextView is 111.0.
如果我在 NSTextView 中使用它,这种字体的行高是 111.0.0。
NSLayoutManager *lm = [[NSLayoutManager alloc] init];
NSLog(@"%f", [lm defaultLineHeightForFont:font]); // this is 111.0
Now, if I do the same thing with Core Text, the result is 110.4 (assuming you can calculate the line height by adding the ascent, descent and leading).
现在,如果我对 Core Text 做同样的事情,结果是 110.4(假设您可以通过添加上升、下降和前导来计算行高)。
CTFontRef cFont = CTFontCreateWithName(CFSTR("Times New Roman"), 96.0, NULL);
NSLog(@"%f", CTFontGetDescent(cFont) + CTFontGetAscent(cFont) +
CTFontGetLeading(cFont)); // this is 110.390625
This is very close to 111.0, but for some fonts the difference is much bigger. E.g. for Helvetica, NSLayoutManager gives 115.0 whereas CTFont ascent + descent + leading = 96.0. Clearly, for Helvetica, I wouldn't be able to use ascent + descent + leading to calculate the spacing between lines.
这非常接近 111.0,但对于某些字体,差异要大得多。例如,对于 Helvetica,NSLayoutManager 给出 115.0 而 CTFont ascent +escent +leading = 96.0。显然,对于 Helvetica,我将无法使用上升 + 下降 + 引导来计算行间距。
So I thought I'd use CTFrame and CTFramesetter to layout a few lines and get the linespacing from that. But that also gives different values.
所以我想我会使用 CTFrame 和 CTFramesetter 来布局几行并从中获得行间距。但这也给出了不同的值。
CTFontRef cFont = CTFontCreateWithName(CFSTR("Times New Roman"), 96.0, NULL);
NSDictionary *attrs = [NSDictionary dictionaryWithObject:(id)cFont forKey:(id)kCTFontAttributeName];
NSAttributedString *threeLines = [[NSAttributedString alloc] initWithString:@"abcdefg\nabcdefg\nabcdefg" attributes:attrs];
CTFramesetterRef threeLineFramesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)threeLines);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0.0, 0.0, 600.0, 600.0));
CTFrameRef threeLineFrame = CTFramesetterCreateFrame(threeLineFramesetter, CFRangeMake(0, 0), path, NULL);
CGPoint lineOrigins[3];
CTFrameGetLineOrigins(threeLineFrame, CFRangeMake(0, 0), lineOrigins);
NSLog(@"space between line 1 and 2: %f", lineOrigins[0].y - lineOrigins[1].y); // result: 119.278125
NSLog(@"space between line 2 and 3: %f", lineOrigins[1].y - lineOrigins[2].y); // result: 113.625000
So the line spacing is now even more different from the 111.0 that was used in my NSTextView, and not every line is equal. It seems that the line breaks add some extra space (even though the default value for paragraphSpacingBefore
is 0.0).
所以行距现在与我的 NSTextView 中使用的 111.0 更加不同,而且不是每一行都是相等的。似乎换行符增加了一些额外的空间(即使默认值为paragraphSpacingBefore
0.0)。
I'm working around this problem now by getting the line height via NSLayoutManager and then individually drawing each CTLine, but I wonder if there's a better way to do this.
我现在正在通过 NSLayoutManager 获取行高然后单独绘制每个 CTLine 来解决这个问题,但我想知道是否有更好的方法来做到这一点。
回答by alastair
OK, so I took a good look at what goes on in the guts of NSLayoutManager, and it appears, based on my reading of the disassembly, that the code it uses boils down to something like this:
好的,所以我仔细查看了 NSLayoutManager 内部发生的事情,根据我对反汇编的阅读,它使用的代码似乎可以归结为以下内容:
CGFloat ascent = CTFontGetAscent(theFont);
CGFloat descent = CTFontGetDescent(theFont);
CGFloat leading = CTFontGetLeading(theFont);
if (leading < 0)
leading = 0;
leading = floor (leading + 0.5);
lineHeight = floor (ascent + 0.5) + floor (descent + 0.5) + leading;
if (leading > 0)
ascenderDelta = 0;
else
ascenderDelta = floor (0.2 * lineHeight + 0.5);
defaultLineHeight = lineHeight + ascenderDelta;
This will get you the 111.0 and 115.0 values for the two fonts you mention above.
这将为您提供上面提到的两种字体的 111.0 和 115.0 值。
I should add that the correct way, according to the OpenType specification, is just to add the three values (being careful, if you're using an API that doesn't make them all positive, to get the sign of the descent value correct).
我应该补充一点,根据 OpenType 规范,正确的方法是添加三个值(小心,如果您使用的 API 不会使它们全部为正,则要使下降值的符号正确)。
回答by user1307179
simple. set up a test string and frame and compare origin of two lines of the font you want. Then if you want to calculate leading just use line height accent descent to do the calculation.
简单的。设置测试字符串和框架并比较所需字体的两行的原点。然后,如果您想计算行距,只需使用行高重音下降来进行计算。
- (float)getLineHeight {
CFMutableAttributedStringRef testAttrString;
testAttrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
NSString *testString = @"testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest";
CFAttributedStringReplaceString (testAttrString, CFRangeMake(0, 0), (CFStringRef)testString);
CTFontRef myFont1 = CTFontCreateWithName((CFStringRef)@"Helvetica", 30, NULL);
CFRange range = CFRangeMake(0,testString.length);
CFAttributedStringSetAttribute(testAttrString, range, kCTFontAttributeName, myFont1);
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds;
if ([model isLandscape]) {
bounds = CGRectMake(0, 10, 1024-20, 768);
}
else {
bounds = CGRectMake(0, 10, 768-20, 1024);
}
CGPathAddRect(path, NULL, bounds);
CTFramesetterRef testFramesetter = CTFramesetterCreateWithAttributedString(testAttrString);
CTFrameRef testFrameRef = CTFramesetterCreateFrame(testFramesetter,CFRangeMake(0, 0), path, NULL);
CGPoint origins1,origins2;
CTFrameGetLineOrigins(testFrameRef, CFRangeMake(0, 1), &origins1);
CTFrameGetLineOrigins(testFrameRef, CFRangeMake(1, 1), &origins2);
return origins1.y-origins2.y;
}
回答by alastair
Have you looked to see what the sign of the value returned by CTFontGetDescent()
is? A common mistake is to assume that descent values are positive, when in fact they tend to be negative (to reflect the fact that they are a descent belowthe font baseline).
您是否查看过返回值的符号CTFontGetDescent()
是什么?一个常见的错误是假设下降值是正值,而实际上它们往往是负值(以反映它们是字体基线以下的下降值)。
As a result, line spacing should probably be set to
因此,行间距可能应该设置为
ascent - descent + leading