Подтвердить что ты не робот

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

4b9b3361

Ответ 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