Jak działają odstępy między wierszami w tekście głównym? (i dlaczego różni się od NSLayoutManager?)

Próbuję narysować tekst za pomocą podstawowych funkcji tekstowych, z odstępami między wierszami, które są jak najbardziej zbliżone do tego, co byłoby, gdybym użył NSTextView.

Weź tę czcionkę jako przykład:

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

Wysokość linii tej czcionki, jeśli użyłbym jej w NSTextView to 111.0.

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

Teraz, jeśli zrobię to samo z tekstem głównym, wynik wynosi 110.4 (zakładając, że możesz obliczyć wysokość linii dodając wejście, zejście i prowadzenie).

CTFontRef cFont = CTFontCreateWithName(CFSTR("Times New Roman"), 96.0, NULL);
NSLog(@"%f", CTFontGetDescent(cFont) + CTFontGetAscent(cFont) + 
             CTFontGetLeading(cFont)); // this is 110.390625

To jest bardzo blisko 111.0, ale dla niektórych czcionek różnica jest znacznie większa. Np. dla Helvetica, NSLayoutManager daje 115.0, podczas gdy ctfont ascent + descent + leading = 96.0 . Oczywiście w przypadku Helvetiki nie byłbym w stanie użyć ascent + descent + leading do obliczenia odstępów między liniami.

Więc pomyślałem, że użyję CTFrame i CTFramesetter do ułożenia kilku linii i uzyskania z tego przeplotu linii. Ale to również daje różne wartości.

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

Więc odstępy między wierszami są teraz jeszcze bardziej różne od 111.0, który był używany w moim NSTextView, a nie każda linia jest równa. Wygląda na to, że podziały linii dodają trochę dodatkowego miejsca (chociaż domyślną wartością paragraphSpacingBefore jest 0.0).

Pracuję teraz nad tym problemem, pobierając wysokość linii za pomocą NSLayoutManager, a następnie indywidualnie rysując każdą linię CTLine, ale zastanawiam się, czy jest na to lepszy sposób.

Author: Steven Vandeweghe, 2011-04-01

3 answers

OK, więc dobrze przyjrzałem się temu, co dzieje się w bebechach NSLayoutManager, i wydaje się, na podstawie mojego odczytu disassembly, że kod, którego używa sprowadza się do czegoś takiego:

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;

Dzięki temu uzyskasz wartości 111.0 i 115.0 dla dwóch wymienionych powyżej czcionek.

Powinienem dodać, że poprawnym sposobem, zgodnie ze specyfikacją OpenType, jest dodanie trzech wartości (uważając, jeśli używasz API, które nie czyni z nich wszystkich pozytywnych, aby uzyskać znak wartości zejścia poprawny).

 43
Author: alastair,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-12 13:29:48

Proste. skonfiguruj testowy ciąg i ramkę i porównaj pochodzenie dwóch linii czcionki, którą chcesz. Następnie, jeśli chcesz obliczyć interlinię, użyj zniżania z akcentem wysokości linii, aby wykonać obliczenia.

    - (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;
    }
 4
Author: user1307179,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-07 19:06:28

Czy sprawdzałeś, jaki jest znak wartości zwracanej przez CTFontGetDescent()? Częstym błędem jest założenie, że wartości opadania są dodatnie, podczas gdy w rzeczywistości wydają się być ujemne (aby odzwierciedlić fakt, że są one zejściem poniżej linii bazowej czcionki).

W rezultacie odstępy między wierszami powinny być prawdopodobnie ustawione na

ascent - descent + leading
 1
Author: alastair,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-08 10:39:33