Как работает межстрочный интервал в Core Text? (и почему он отличается от NSLayoutManager?) - PullRequest
26 голосов
/ 01 апреля 2011

Я пытаюсь нарисовать текст, используя функции 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 + lead = 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

Таким образом, межстрочный интервал теперь еще больше отличается от 111.0, который использовался в моем NSTextView, и не каждая строка равна.Кажется, что разрывы строк добавляют дополнительный пробел (хотя значение по умолчанию для paragraphSpacingBefore равно 0,0).

Сейчас я работаю над этой проблемой, получая высоту строки через NSLayoutManager, а затем по отдельности рисуя каждыйCTLine, но мне интересно, есть ли лучший способ сделать это.

Ответы [ 3 ]

45 голосов
/ 12 апреля 2011

ОК, поэтому я внимательно посмотрел на то, что происходит внутри 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, который не делает их все положительными, чтобы получить знак правильное значение спуска).

4 голосов
/ 07 декабря 2012

простой. установите тестовую строку и фрейм и сравните начало двух строк шрифта, который вы хотите. Затем, если вы хотите рассчитать начальное значение, просто используйте высотный акцент, чтобы выполнить расчет.

    - (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;
    }
1 голос
/ 08 апреля 2011

Вы смотрели, чтобы увидеть, каков знак значения, возвращаемого CTFontGetDescent()? Распространенной ошибкой является допущение, что значения спуска положительны, когда на самом деле они имеют тенденцию быть отрицательными (чтобы отразить тот факт, что они имеют спуск ниже базовой линии шрифта).

В результате межстрочный интервал, вероятно, должен быть установлен на

ascent - descent + leading
...