简体   繁体   English

行间距如何在Core Text中工作? (为什么它与NSLayoutManager不同?)

[英]How does line spacing work in Core Text? (and why is it different from NSLayoutManager?)

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。

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. 这非常接近111.0,但对于某些字体,差异要大得多。 Eg for Helvetica, NSLayoutManager gives 115.0 whereas CTFont ascent + descent + leading = 96.0. 例如,对于Helvetica,NSLayoutManager给出115.0而CTFont上升+下降+领先= 96.0。 Clearly, for Helvetica, I wouldn't be able to use ascent + descent + leading to calculate the spacing between lines. 很明显,对于Helvetica,我无法使用上升+下降+导致计算线之间的间距。

So I thought I'd use CTFrame and CTFramesetter to layout a few lines and get the linespacing from that. 所以我想我会使用CTFrame和CTFramesetter来布局几行并从中获取行间距。 But that also gives different values. 但这也给出了不同的价值观。

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. 因此行间距现在与我的NSTextView中使用的111.0更加不同,并不是每条线都相等。 It seems that the line breaks add some extra space (even though the default value for paragraphSpacingBefore is 0.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,但我想知道是否有更好的方法来做到这一点。

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不能使它们都是正数,那么得到下降值的符号是正确的)。

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

Have you looked to see what the sign of the value returned by CTFontGetDescent() is? 您是否想看看CTFontGetDescent()返回的值的符号是什么? 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 below the font baseline). 一个常见的错误是假设下降值是正的,而实际上它们往往是负的(以反映它们是低于字体基线的下降的事实)。

As a result, line spacing should probably be set to 因此,行间距应该设置为

ascent - descent + leading

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM