29

我正在尝试使用 Core Text 函数绘制文本,其行距尽可能接近使用 NSTextView 时的行距。

以这个字体为例:

NSFont *font = [NSFont fontWithName:@"Times New Roman" size:96.0];

如果我在 NSTextView 中使用,这种字体的行高是 111.0。

NSLayoutManager *lm = [[NSLayoutManager alloc] init];
NSLog(@"%f", [lm defaultLineHeightForFont:font]); // this is 111.0

现在,如果我对 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

这非常接近 111.0,但对于某些字体,差异要大得多。例如对于 Helvetica,NSLayoutManager 给出 115.0 而 CTFont ascent + descent +leading = 96.0。显然,对于 Helvetica,我无法使用上升 + 下降 + 引导来计算行间距。

所以我想我会使用 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

所以现在的行间距与我在 NSTextView 中使用的 111.0 更加不同,而且并不是每一行都是相等的。似乎换行符增加了一些额外的空间(即使默认值为paragraphSpacingBefore0.0)。

我现在正在通过 NSLayoutManager 获取行高然后单独绘制每个 CTLine 来解决这个问题,但我想知道是否有更好的方法来做到这一点。

4

3 回答 3

50

好的,所以我仔细研究了 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;

这将为您提供上面提到的两种字体的 111.0 和 115.0 值。

我应该补充一点,根据 OpenType 规范,正确的方法就是添加三个值(请注意,如果您使用的 API 不会使它们全部为正,则要使下降值的符号正确)。

于 2011-04-12T13:29:48.907 回答
4

简单的。设置一个测试字符串和框架,并比较你想要的两行字体的来源。然后,如果您想计算领先,只需使用行高重音下降来进行计算。

    - (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;
    }
于 2012-12-07T19:06:28.500 回答
1

您是否看过返回值的符号CTFontGetDescent()是什么?一个常见的错误是假设下降值是正的,而实际上它们往往是负的(以反映它们是低于字体基线的下降的事实)。

因此,行距可能应该设置为

ascent - descent + leading
于 2011-04-08T10:39:33.427 回答