29

Core Text 関数を使用して、NSTextView を使用した場合に可能な限り近い行間でテキストを描画しようとしています。

例として、次のフォントを取り上げます。

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

NSTextView で使用する場合、このフォントの行の高さは 111.0.0 です。

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

ここで、コア テキストで同じことを行うと、結果は 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

したがって、行間隔は、私の NSTextView で使用されていた 111.0 とはさらに異なり、すべての行が等しいわけではありません。改行によって余分なスペースが追加されているようです (のデフォルト値paragraphSpacingBeforeは 0.0 ですが)。

NSLayoutManager を介して行の高さを取得し、各 CTLine を個別に描画することで、この問題を回避していますが、これを行うためのより良い方法があるかどうか疑問に思っています。

4

3 に答える 3

50

OK、それで 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;

これにより、上記の 2 つのフォントの 111.0 と 115.0 の値が得られます。

OpenType 仕様によると、正しい方法は 3 つの値を加算することであることを付け加えておきます (すべての値を正にしない API を使用している場合は、降下値の符号を正しく取得するために注意してください)。 )。

于 2011-04-12T13:29:48.907 に答える
4

単純。テスト文字列とフレームを設定し、必要なフォントの 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;
    }
于 2012-12-07T19:06:28.500 に答える
1

によって返される値の符号が何であるかを調べましたCTFontGetDescent()か? よくある間違いは、ディセント値が正であると仮定することです。実際には負になる傾向があります (フォントのベースラインより下のディセントであるという事実を反映するため)。

その結果、行間隔はおそらく次のように設定する必要があります

ascent - descent + leading
于 2011-04-08T10:39:33.427 に答える