Giving Framesetter the correct line spacing adjustment

微笑、不失礼 提交于 2019-11-30 15:53:33

I too ran into this and here is the code that worked in a real project:

// When you create an attributed string the default paragraph style has a leading 
// of 0.0. Create a paragraph style that will set the line adjustment equal to
// the leading value of the font. This logic will ensure that the measured
// height for a given paragraph of attributed text will be accurate wrt the font.

- (void) applyParagraphAttributes:(CFMutableAttributedStringRef)mAttributedString
{
  CGFloat leading = CTFontGetLeading(self.plainTextFont);

  CTParagraphStyleSetting paragraphSettings[1] = {
    kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &leading
  };

  CTParagraphStyleRef  paragraphStyle = CTParagraphStyleCreate(paragraphSettings, 1);

  CFRange textRange = CFRangeMake(0, [self length]);

  CFStringRef keys[] = { kCTParagraphStyleAttributeName };
  CFTypeRef values[] = { paragraphStyle };

  CFDictionaryRef attrValues = CFDictionaryCreate(kCFAllocatorDefault,
                                                  (const void**)&keys,
                                                  (const void**)&values,
                                                  sizeof(keys) / sizeof(keys[0]),
                                                  &kCFTypeDictionaryKeyCallBacks,
                                                  &kCFTypeDictionaryValueCallBacks);

  BOOL clearOtherAttributes = FALSE;
  CFAttributedStringSetAttributes(mAttributedString, textRange, attrValues, (Boolean)clearOtherAttributes);
  CFRelease(attrValues);

  CFRelease(paragraphStyle);

  self.stringRange = textRange;

  return;
}

Answers to the 3 questions I had above:

  1. Yes, “leading” really what is meant by kCTParagraphStyleSpecifierLineSpacingAdjustment. Or at any rate, it works as expected.
  2. Use CTFontGetLeading(fontRef) to get the font's normal leading, or plug in whatever value (as a CGFloat) you choose.
  3. N/A.

Answers 1 and 2 work: Specifying a leading value in a paragraphStyle attribute of your attributed string will enable the Core-Text framesetter to calculate its height exactly.

There are two caveats:

  1. If you try to calculate heights incrementally, one string at a time, each string containing an initial line break, framesetter will consider that line break to represent an entire line, not just the leading. If you want the height of the concatenated strings, you have to feed that concatenation to the framesetter. Of course, you could keep track of the incremental height differences, but there's no way to avoid having framesetter recalculate the earlier string dimensions.
  2. CATextLayer ignores spacing adjustments (and other attributes). If framing per exact string height is an issue, you must draw direct to a CALayer.

And there is one mystery: What is going on with UIFont's deprecated leading? Leading and lineHeight are two distinct things.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!