介绍CoreText的一些概念
 
 
Core Text基础 
CoreText 是用于处理文字和字体的底层技术。它直接和 Core Graphics(又被称为 Quartz)打交道。
 UIWebView 也是处理复杂的文字排版的备选方案。对于排版,基于 CoreText 和基于 UIWebView 相比,前者有以下好处:
CoreText 占用的内存更少,渲染速度快,UIWebView 占用的内存更多,渲染速度慢。
CoreText 在渲染界面前就可以精确地获得显示内容的高度(只要有了 CTFrame 即可),而 UIWebView 只有渲染出内容后,才能获得内容的高度(而且还需要用 javascript 代码来获取)
CoreText 的 CTFrame 可以在后台线程渲染,UIWebView 的内容只能在主线程(UI 线程)渲染。
基于 CoreText 可以做更好的原生交互效果,交互效果可以更细腻。而 UIWebView 的交互效果都是用 javascript 来实现的,在交互效果上会有一些卡顿存在。例如,在 UIWebView 下,一个简单的按钮按下效果,都无法做到原生按钮的即时和细腻的按下效果。
 
当然,基于 CoreText 的排版方案也有一些劣势:
CoreText 渲染出来的内容不能像 UIWebView 那样方便地支持内容的复制。
基于 CoreText 来排版需要自己处理很多复杂逻辑,例如需要自己处理图片与文字混排相关的逻辑,也需要自己实现链接点击操作的支持。
 
以下是Core Text的构成:
CTFrame可以想象成画布, 画布的大小范围由CGPath决定 
CTFrame由很多CTLine组成, CTLine表示为一行 
CTLine由多个CTRun组成, CTRun相当于一行中的多个块, 但是CTRun不需要你自己创建, 由NSAttributedString的属性决定, 系统自动生成。每个CTRun对应不同属性 
CTFramesetter是一个工厂, 创建CTFrame, 一个界面上可以有多个CTFrame 
 
字体 
字体(Font):是一系列字号、样式和磅值相同的字符(例如:10磅黑体Palatino)。现多被视为字样的同义词 
字面(Face):是所有字号的磅值和格式的综合 
字体集(Font family):是一组相关字体(例如:Franklin family包括Franklin Gothic、Fran-klinHeavy和Franklin Compressed) 
磅值(Weight):用于描述字体粗度。典型的磅值,从最粗到最细,有极细、细、book、中等、半粗、粗、较粗、极粗 
样式(Style):字形有三种形式:Roman type是直体;oblique type是斜体;utakuc type是斜体兼曲线(比Roman type更像书法体)。 
x高度(X height):指小写字母的平均高度(以x为基准)。磅值相同的两字母,x高度越大的字母看起来比x高度小的字母要大 
Cap高度(Cap height):与x高度相似。指大写字母的平均高度(以C为基准) 
下行字母(Descender):例如在字母q中,基线以下的字母部分叫下伸部分 
上行字母(Ascender):x高度以上的部分(比如字母b)叫做上伸部分 
基线(Baseline):通常在x、v、b、m下的那条线 描边(Stroke):组成字符的线或曲线。可以加粗或改变字符形状 
 
CoreText定义的字体 样式 
可以在 CTStringAttributes.h 中找到
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 const  CFStringRef  kCTCharacterShapeAttributeName;const  CFStringRef  kCTFontAttributeName;const  CFStringRef  kCTKernAttributeName;const  CFStringRef  kCTLigatureAttributeName;const  CFStringRef  kCTForegroundColorAttributeName;const  CFStringRef  kCTForegroundColorFromContextAttributeName;  const  CFStringRef  kCTParagraphStyleAttributeName;const  CFStringRef  kCTStrokeWidthAttributeName;const  CFStringRef  kCTStrokeColorAttributeName;const  CFStringRef  kCTSuperscriptAttributeName;const  CFStringRef  kCTUnderlineColorAttributeName;const  CFStringRef  kCTUnderlineStyleAttributeName;const  CFStringRef  kCTVerticalFormsAttributeName;const  CFStringRef  kCTGlyphInfoAttributeName;const  CFStringRef  kCTRunDelegateAttributeName
 
CoreText坐标系 
从图中可看出CoreText坐标系是以左下角为坐标原点,而我们常使用的UIKit是以左上角为坐标原点,因此在CoreText中的布局完成后需要对其坐标系进行转换,否则直接绘制出现位置反转的镜像情况。
CoreText 图文混排 CoreText实际上并没有相应API直接将一个图片转换为CTRun并进行绘制,它所能做的只是为图片预留相应的空白区域,而真正的绘制则是交由CoreGraphics完成。(像OSX就方便很多,直接将图片打包进NSTextAttachment即可,根本无须操心绘制的事情,所以基于这个想法,M80AttributedLabel的接口和实现也是使用了attachment这么个概念,图片或者UIView都是被当作文字段中的attachment。)
在CoreText中提供了CTRunDelegate这么个Core Foundation类,顾名思义它可以对CTRun进行拓展:AttributedString某个段设置kCTRunDelegateAttributeName属性之后,CoreText使用它生成CTRun是通过当前Delegate的回调来获取自己的ascent,descent和width,而不是根据字体信息。这样就给我们留下了可操作的空间:用一个空白字符作为图片的占位符,设好Delegate,占好位置,然后用CoreGraphics进行图片的绘制。
基本使用步骤 
新建一个UIView类,在其drawRect方法中
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 -(void )drawRect:(CGRect )rect{     [super  drawRect:rect];               CGContextRef  context = UIGraphicsGetCurrentContext ();          CGContextSetTextMatrix (context, CGAffineTransformIdentity );     CGContextTranslateCTM (context , 0 , self .bounds.size.height);     CGContextScaleCTM (context, 1.0 , -1.0 );          CGMutablePathRef  path = CGPathCreateMutable ();     CGPathAddRect (path , NULL  , self .bounds);          NSAttributedString * attString = [[NSAttributedString  alloc]initWithString:@"Hello CoreText" ];          CTFramesetterRef  framesetter = CTFramesetterCreateWithAttributedString ((CFAttributedStringRef )attString);          CTFrameRef  frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0 , [attString length]), path , NULL );          CTFrameDraw (frame, context);               CFRelease (frame);     CFRelease (path);     CFRelease (framesetter);      } 
 
Hello CoreText就会显示在该View上了,这是CoreText的基本使用步骤。
简单的富文本 
在上面代码的基础上,为NSAttributeString添加相关的字体信息
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 NSMutableAttributedString * mAttStr = [[NSMutableAttributedString  alloc]initWithString:@"Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;Hello CoreText ;" ];CTFontRef  font = CTFontCreateWithName (CFSTR ("Georgia" ), 40 , NULL );[mAttStr addAttribute:(id )kCTFontAttributeName value:(__bridge id  _Nonnull)(font)  range:NSMakeRange (0 , 4 )]; CTFontRef  font2 = CTFontCreateWithName ((CFStringRef )[UIFont  italicSystemFontOfSize:20 ].fontName, 14 , NULL );[mAttStr addAttribute:(id )kCTFontAttributeName value:(__bridge id  _Nonnull)(font2)  range:NSMakeRange (6 , 4 )]; [mAttStr addAttribute:(id )kCTUnderlineStyleAttributeName value:(id )[NSNumber  numberWithInt:kCTUnderlineStyleDouble] range:NSMakeRange (18 , 4 )]; [mAttStr addAttribute:(id )kCTUnderlineStyleAttributeName value:(id )[NSNumber  numberWithInt:kCTUnderlineStyleSingle] range:NSMakeRange (25 , 4 )]; [mAttStr addAttribute:(id )kCTUnderlineColorAttributeName value:(id )[UIColor  redColor].CGColor range:NSMakeRange (25 , 4 )]; long  number = 10 ;CFNumberRef  num = CFNumberCreate (kCFAllocatorDefault,kCFNumberSInt8Type,&number);[mAttStr addAttribute:(id )kCTKernAttributeName value:(__bridge id )num range:NSMakeRange (36 , 4 )]; [mAttStr addAttribute:(id )kCTForegroundColorAttributeName value:(id )[UIColor  redColor].CGColor range:NSMakeRange (43 , 4 )]; long  number2 = 2 ;CFNumberRef  num2 = CFNumberCreate (kCFAllocatorDefault,kCFNumberSInt8Type,&number2);[mAttStr addAttribute:(id )kCTStrokeWidthAttributeName value:(__bridge id )num2 range:NSMakeRange (54 , 4 )]; 
 
效果图:
更多的样式 
在介绍如何绘制更多样式之前,需要介绍 按Line绘制 CTLineDraw 和按Run绘制 CTRunDraw
 
CTLineDraw 按行绘制 将上面的
1 CTFrameDraw (frame, context);
 
替换成:
1 2 3 4 5 6 7 8 9 10 11 12 13 CFArrayRef  lines = CTFrameGetLines (frame);CFIndex  numOfLines =  CFArrayGetCount (lines);CGPoint  lineOrigins[CFArrayGetCount (lines)];CTFrameGetLineOrigins (frame, CFRangeMake (0 , 0 ), lineOrigins);for  (int  i = 0  ; i < numOfLines; i++) {    CTLineRef  line = CFArrayGetValueAtIndex (lines, i);          CGContextSetTextPosition (context, lineOrigins[i].x, lineOrigins[i].y);          CTLineDraw (line , context); } 
 
CTRunDraw 按Run绘制 将上面的
1 CTFrameDraw (frame, context);
 
替换成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CFArrayRef  lines = CTFrameGetLines (frame);CFIndex  numOfLines =  CFArrayGetCount (lines);     CGPoint  lineOrigins[CFArrayGetCount (lines)];CTFrameGetLineOrigins (frame, CFRangeMake (0 , 0 ), lineOrigins);for  (int  i = 0  ; i < numOfLines; i++) {    CTLineRef  line = CFArrayGetValueAtIndex (lines, i);              CGContextSetTextPosition (context, lineOrigins[i].x, lineOrigins[i].y);              CFArrayRef  runs = CTLineGetGlyphRuns (line);     for  (int  j = 0 ; j < CFArrayGetCount (runs); j++) {         CTRunRef  run = CFArrayGetValueAtIndex (runs, j);         CTRunDraw (run, context, CFRangeMake (0 , 0 ));     } } 
 
最终 CTRunDraw CTLineDraw 和 CTFrameDraw的绘制结果都是相同的。
实现删除线效果 
既然我们可以按照一小块CTRun的方式绘制,就可以对一些CTRun做一些自定义绘制
比如在 < CoreText/CTStringArrtibutes.h >中并没有找到删除线的定义,我们可以尝试去实现它。
 
**一、**在NSAttributedString中添加删除线属性(NSStrikethroughStyleAttributeName字段定义在NSAttributedString.h中,coreText没有对应的实现)
1 2 [mAttStr addAttribute:NSStrikethroughStyleAttributeName  value:[NSNumber  numberWithInt:NSUnderlineStyleSingle ] range:NSMakeRange (72 ,18 )]; 
 
**二、**在上面按run绘制的基础上判断一下有无删除线属性,有就绘制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 CFArrayRef  lines = CTFrameGetLines (frame);CFIndex  numOfLines =  CFArrayGetCount (lines);     CGPoint  lineOrigins[CFArrayGetCount (lines)];CTFrameGetLineOrigins (frame, CFRangeMake (0 , 0 ), lineOrigins);for  (int  i = 0  ; i < numOfLines; i++) {    CTLineRef  line = CFArrayGetValueAtIndex (lines, i);              CGContextSetTextPosition (context, lineOrigins[i].x, lineOrigins[i].y);              CFArrayRef  runs = CTLineGetGlyphRuns (line);     for  (int  j = 0 ; j < CFArrayGetCount (runs); j++) {         CTRunRef  run = CFArrayGetValueAtIndex (runs, j);         CTRunDraw (run, context, CFRangeMake (0 , 0 ));                           NSDictionary  *runAttributes = (NSDictionary  *)CTRunGetAttributes (run);                  if (runAttributes[NSStrikethroughStyleAttributeName ] != nil ){                          [self  drawStrikethroughStyleInRun:run attributes:runAttributes context:context];         }     } } 
 
三、  实现方法 -drawStrikethroughStyleInRun: attributes : context :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 -(void )drawStrikethroughStyleInRun:(CTRunRef )run attributes: (NSDictionary *)attributes context:(CGContextRef )context {     CFNumberRef  styleRef = (__bridge CFNumberRef )(attributes[NSStrikethroughStyleAttributeName ]);     NSUnderlineStyle  style = NSUnderlineStyleNone ;     CFNumberGetValue (styleRef, kCFNumberSInt64Type, &style);     if  (style == NSUnderlineStyleNone ) return ;            CGFloat  lineWidth = 1 ;     if ((style & NSUnderlineStyleThick ) == NSUnderlineStyleThick ){         lineWidth = 2 ;     }     CGContextSetLineWidth (context, lineWidth);               CGPoint  firstP = CGPointZero ;     size_t length = CTRunGetGlyphCount (run);     const  CGPoint  *firstGP = CTRunGetPositionsPtr (run);     if  (!firstGP) {         NSMutableData  *tempBuffer = [[NSMutableData  alloc] initWithLength:sizeof (CGPoint ) * length];         CTRunGetPositions (run, CFRangeMake (0 , length), (CGPoint  *)tempBuffer.mutableBytes);         firstGP = tempBuffer.mutableBytes;     }     firstP = *firstGP ;               CGContextBeginPath (context);          CGColorRef  lineColor = (__bridge CGColorRef )attributes[NSStrikethroughColorAttributeName ];     if (lineColor == nil ){         CGContextSetStrokeColorWithColor (context, UIColor .blueColor.CGColor);     }     else {         CGContextSetStrokeColorWithColor (context, lineColor);     }          UIFont * font = attributes[NSFontAttributeName ];     if (font == nil ){         font = [UIFont  systemFontOfSize:UIFont .systemFontSize];     }     CGFloat  lineHeight = font.xHeight / 2.0  + firstP.y;          CGPoint  pt =  CGContextGetTextPosition (context);     lineHeight += pt.y;          float  w = CTRunGetTypographicBounds (run, CFRangeMake (0 , 0 ), nil  , nil , nil );               CGContextMoveToPoint (context, pt.x + firstP.x, lineHeight);     CGContextAddLineToPoint (context, pt.x + firstP.x + w, lineHeight);     CGContextStrokePath (context);      } 
 
实现效果:
实现自定义样式-矩形框标注 
自定义一个TSRectColor  一个包住文字的矩形框,尝试去实现它。
 
一、  在 NSAttributedString 中添加自定义属性:
1 2 3 [mAttStr addAttribute:@"TSRectColor"  value:[UIColor  yellowColor] range:NSMakeRange (90 , 18 )]; 
 
二、  修改一下上面的实现删除线的代码,添加实现矩形框。与实现删除线不一样的是,实现矩形框需要在CTRunDraw之前:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 CFArrayRef  lines = CTFrameGetLines (frame);CFIndex  numOfLines =  CFArrayGetCount (lines);     CGPoint  lineOrigins[CFArrayGetCount (lines)];CTFrameGetLineOrigins (frame, CFRangeMake (0 , 0 ), lineOrigins);for  (int  i = 0  ; i < numOfLines; i++) {    CTLineRef  line = CFArrayGetValueAtIndex (lines, i);              CGContextSetTextPosition (context, lineOrigins[i].x, lineOrigins[i].y);              CFArrayRef  runs = CTLineGetGlyphRuns (line);     for  (int  j = 0 ; j < CFArrayGetCount (runs); j++) {         CTRunRef  run = CFArrayGetValueAtIndex (runs, j);                  NSDictionary  *runAttributes = (NSDictionary  *)CTRunGetAttributes (run);         if (runAttributes[@"TSRectColor" ]){                          [self  drawRectColorInRun:run  attributes:runAttributes context:context];         }         CTRunDraw (run, context, CFRangeMake (0 , 0 ));                  if (runAttributes[NSStrikethroughStyleAttributeName ] != nil ){                          [self  drawStrikethroughStyleInRun:run attributes:runAttributes context:context];         }     } } 
 
三、  实现 -drawRectColorInRun: attributes: context:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 -(void )drawRectColorInRun:(CTRunRef )run attributes: (NSDictionary *)attributes context:(CGContextRef )context {          UIColor * color = attributes[@"TSRectColor" ];     if (!color)return ;          CGPoint  firstPoint = [self  firstPointInRun:run];          CGFloat  ascent,descent;     double  typographicBounds = CTRunGetTypographicBounds (run , CFRangeMake (0 , 0 ), &ascent, &descent, NULL );          CGPoint  textP = CGContextGetTextPosition (context);          CGRect  rect = CGRectMake (firstPoint.x + textP.x, textP.y + firstPoint.y - descent, typographicBounds, ascent + descent);          CGContextSetStrokeColorWithColor (context , color.CGColor);     CGContextAddRect (context, rect);     CGContextStrokePath (context); } 
 
实现效果:
添加超链接 
如何识别文字中的超链接 不同场景有不同的处理方式,这里就不处理了。
 
添加超链接 简单来讲就是重写touchesBegan方法 获取点击的点,再到CTFrameRef中比较
 
重写touchesBegan方法 根据touch事件获取点point
1 2 3 4 5 6 7 8 -(void )touchesBegan:(NSSet <UITouch  *> *)touches withEvent:(UIEvent  *)event{     UITouch * touch = [touches anyObject];     CGPoint  point = [touch locationInView:self ];     bool  ifClicked = [self  ifClickLink:point];     if  (ifClicked) {         NSLog (@"点击了连接" );     } } 
 
判断touch点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 -(bool )ifClickLink:(CGPoint )point{     if  (!CGRectContainsPoint (self .bounds, point)) {         return  false ;     }          CFArrayRef  lines = CTFrameGetLines (self .textFrame);     if (!lines) return  false ;     CFIndex  count = CFArrayGetCount (lines);     CGPoint  origins[count];     CTFrameGetLineOrigins (self .textFrame, CFRangeMake (0 , 0 ), origins);          CGAffineTransform  transform = CGAffineTransformScale (CGAffineTransformMakeTranslation (0 , self .bounds.size.height), 1. f, -1. f);          for  (int  i = 0  ; i < count; i++) {         CGPoint  linePoint = origins[i];         CTLineRef  line = CFArrayGetValueAtIndex (lines , i);                           CGFloat  ascent = 0.0 f;         CGFloat  descent = 0.0 f;         CGFloat  leading = 0.0 f;         CGFloat  width = (CGFloat )CTLineGetTypographicBounds (line, &ascent, &descent, &leading);         CGFloat  height = ascent + descent;         CGRect  flippedLineRect = CGRectMake (linePoint.x, linePoint.y - descent, width, height);         CGRect  lineRect = CGRectApplyAffineTransform (flippedLineRect, transform);                           if  (CGRectContainsPoint (lineRect, point)) {                          CFIndex  idx = CTLineGetStringIndexForPosition (line, point);             NSLog (@"%ld" ,idx);                          return  [self  ifClickRangeContainIndex:idx];         }     }          return  false ; } 
 
这样就监测到了点击事件,并且知道点击了哪个字符。再做相关超链接的点击逻辑,就添加超链接功能成功了。
以上代码都在 https://github.com/sunyanyan/TSCode/blob/master/CoreText/CoreText/TestView.m  
 
图文混排 
CoreText是不直接支持绘制图片的,但是我们可以先在需要显示图片的地方用一个特殊的空白占位符代替,同时设置该字体的CTRunDelegate信息为要显示的图片的宽度和高度,这样绘制文字的时候就会先把图片的位置留出来,再在drawRect方法里面用CGContextDrawImage绘制图片。
 
在NSMutableString中添加空白字符为图片占位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 -(void )addImageAttStr{                    CTRunDelegateCallbacks  callbacks;     memset(&callbacks, 0 , sizeof (CTRunDelegateCallbacks ));     callbacks.version = kCTRunDelegateVersion1;     callbacks.getAscent = ascentCallback;     callbacks.getDescent = descentCallback;     callbacks.getWidth = widthCallback;               NSDictionary  *imgInfoDic = @{@"width" :@100 ,@"height" :@30 };          CTRunDelegateRef  delegate = CTRunDelegateCreate (&callbacks, (__bridge void  *)imgInfoDic);          unichar  objectReplacementChar = 0xFFFC ;     NSString  *content = [NSString  stringWithCharacters:&objectReplacementChar length:1 ];     NSMutableAttributedString  *space = [[NSMutableAttributedString  alloc] initWithString:content];     CFAttributedStringSetAttribute ((CFMutableAttributedStringRef )space, CFRangeMake (0 , 1 ), kCTRunDelegateAttributeName, delegate);     CFRelease (delegate);          [self .sampleAttStr insertAttributedString:space atIndex:50 ];   } 
 
其中用到的CTRun delegate回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static  CGFloat  ascentCallback(void  *ref) {         return  [(NSNumber  *)[(__bridge NSDictionary  *)ref objectForKey:@"height" ] floatValue]; } static  CGFloat  descentCallback(void  *ref) {         return  0 ; } static  CGFloat  widthCallback(void  *ref) {         return  [(NSNumber  *)[(__bridge NSDictionary  *)ref objectForKey:@"width" ] floatValue]; } 
 
然后在CTFrameDraw之后用CGContextDrawImage绘制图片
1 2 3 UIImage  *image = [UIImage  imageNamed:@"one.png" ];CGContextDrawImage (context, [self  calculateImagePositionInCTFrame:frame], image.CGImage);
 
用到的calculateImagePositionInCTFrame方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 - (CGRect )calculateImagePositionInCTFrame:(CTFrameRef )ctFrame {                   NSArray  *lines = (NSArray  *)CTFrameGetLines (ctFrame);     NSInteger  lineCount = [lines count];     CGPoint  lineOrigins[lineCount];     CTFrameGetLineOrigins (ctFrame, CFRangeMake (0 , 0 ), lineOrigins);                   for  (NSInteger  i = 0  ; i < lineCount; i++) {                  CTLineRef  line = (__bridge CTLineRef )lines[i];         NSArray  *runObjArray = (NSArray  *)CTLineGetGlyphRuns (line);                               for  (id  runObj in  runObjArray) {                          CTRunRef  run = (__bridge CTRunRef )runObj;             NSDictionary  *runAttributes = (NSDictionary  *)CTRunGetAttributes (run);             CTRunDelegateRef  delegate = (__bridge CTRunDelegateRef )[runAttributes valueForKey:(id )kCTRunDelegateAttributeName];             if  (delegate == nil ) {                 continue ;             }                          NSDictionary  *metaDic = CTRunDelegateGetRefCon (delegate);             if  (![metaDic isKindOfClass:[NSDictionary  class ]]) {                 continue ;             }                          CGRect  runBounds;             CGFloat  ascent;             CGFloat  descent;                          runBounds.size.width = CTRunGetTypographicBounds (run, CFRangeMake (0 , 0 ), &ascent, &descent, NULL );             runBounds.size.height = ascent + descent;                          CGFloat  xOffset = CTLineGetOffsetForStringIndex (line, CTRunGetStringRange (run).location, NULL );             runBounds.origin.x = lineOrigins[i].x + xOffset;             runBounds.origin.y = lineOrigins[i].y;             runBounds.origin.y -= descent;                          CGPathRef  pathRef = CTFrameGetPath (ctFrame);             CGRect  colRect = CGPathGetBoundingBox (pathRef);                          CGRect  delegateBounds = CGRectOffset (runBounds, colRect.origin.x, colRect.origin.y);             return  delegateBounds;         }     }     return  CGRectZero ; } 
 
如图: