Как межстрочный интервал работает в основном тексте? (и почему он отличается от NSLayoutManager?)
Я пытаюсь нарисовать текст, используя функции 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 восхождение + спуск + начало = 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, но мне интересно, есть ли лучший способ сделать это.
Ответы
Ответ 1
Хорошо, поэтому я хорошо посмотрел на то, что происходит в кишках 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, который не делает их всех положительными, чтобы получить знак правильного значения спуска).
Ответ 2
просто. настроить тестовую строку и фрейм и сравнить источник двух строк шрифта, который вы хотите. Затем, если вы хотите рассчитать, просто используйте выравнивание по высоте линии, чтобы выполнить расчет.
- (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;
}
Ответ 3
Вы посмотрели, что означает знак значения, возвращаемого CTFontGetDescent()
? Распространенная ошибка состоит в том, чтобы предположить, что значения спуска являются положительными, когда на самом деле они имеют тенденцию быть отрицательными (чтобы отразить тот факт, что они являются нисходящими ниже базовой линии шрифта).
В результате, межстрочный интервал, вероятно, должен быть установлен на
ascent - descent + leading