CoreText-文字排版

 一、Core Text简介

Core Text是苹果官方提供的底层文字排版引擎,通过它,我们可以精确地控制文字的排版:如文字颜色、字体、字与字间距、行间距。甚至可以精确地控制每行的位置,计算某串文字的范围。正因为它有如此强大的功能,好多富文本的效果,都是通过它来实现的。而且使用它来做富文本效果,比通过UIWebview+js,效率更高,内存占用更少。只是其入门门槛较高,使用上稍微有点麻烦。

二、Core Text类及其关系(图片来自Apple官方):


CTFramesetter:

 通过一个属性字符串初始化它,通过它可以创建CTFrame

CTFrame:

 可以理解成一段文字,这段文字可能是创建CTFramesetter时,所使用的属性字符串的部分或者全部。

CTLine:

 CTFrame又是由一行一行的文字组成,每一行文字对应一个CTLine对象;

可以从CTFrame中获取,也可以单独通过一个属性字符串创建它

通过相关API,可以获取到CTLine的位置,在CTFrame所代表的字符串中的范围,可以获取这一行文字的上行距离(ascend)、下行距离(descent)

CTRun: 

每行文字又是由一个一个的字符组成,而每一个字符对应一个CTRun对象。

由CoreText自动创建,我们只需通过相关API,从CTLine中获取即可。


三、排版-相关属性(图片来自Apple官方)



1、红色线条:BaseLine 是一条假想出来的线,

2、Ascned: 上行距离,即baseLine 到 该行文字最高点的距离

3、Descend: 下行距离,即BaseLine到该行文字最低点的距离

所以在有emoji表情的文字中,因为emoji的descent比文字的descent大,所以整行的descent的为emoji的descnet,

所以会出现在有emoji的一段文字中,行间距不一致的情况。

特别说明:

1、通过相关CoreText API 获取到的Descend是大于0的,而不是向网上其他查到的资料所说的是小于0的

i.e  CTLineGetTypographicBounds ( CTLineRef line, CGFloat *ascent, CGFloat *descent, CGFloat *leading )

所获取到的descent是大于0的

2、UIFont的descender属性,倒是小于0的


四、坐标系

1、UIKit的坐标系是左上角为原点,X轴向右为正,Y轴向下为正

2、而Core Text使用的坐标系是左下角为原点,X轴向右为正,Y轴向上为正。

当进入drawRect方法前,为了与UIKit的坐标系一致,系统内部会自动翻转了一下坐标系,变成左上角为原点

所以进入到drawRect方法时,若没有再次进行坐标系的翻转、平移,直接通过CoreText进行文字排版,会出现文字镜像倒置的现象。

再次翻转坐标系,变成左下角为原点:

CGContextRef ctx = UIGraphicsGetCurrentContext();
//平移
CGContextTranslateCTM(ctx, 0, rect.size.height);
//翻转Y轴
CGContextScaleCTM(ctx, 1, -1);
//设置文字矩阵
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);

五、一个实例

- (void)drawRect:(CGRect)rect {
    if (_text.length == 0) {
        return;
    }
   
    rect.size.height = SYTLabel_MAX_Height;
    
    //将坐标系再次变为原点在左下角的笛卡尔坐标系
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(ctx, 0, rect.size.height);
    CGContextScaleCTM(ctx, 1, -1);
    CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
    
    //通过要渲染的文本,创建FrameSetter
    CTFramesetterRef setter = [self createFrameSetterWithText:_text];
   
    //创建一帧 指定画在rect的矩阵区域内
    CTFrameRef frame = [self createFrameInRect:rect fromSetter:setter];
    
    //获取所有CTLine对象
    NSArray *lines = (__bridge NSArray*)CTFrameGetLines(frame);
    NSInteger count = lines.count;
    CGPoint origins[count];
    //获取每行的原点 注意这个原点是在BaseLine上的 请参考三、排版属性下的那张图
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
    
    //此时坐标原点左下角 第一行的起始Y值 = rect.size.height - 行高 (这跟我们的书写习惯是一致的)
    CGFloat y = rect.size.height;
    //行高=font.ascender-_font.descender 为什么是减掉descender呢? 因为UIFont的descender是小于0的
    //这里行高使用字体的行高 是为了避免文本中存在emoji 导致行间距不一致
    CGFloat lineH = ceil(_font.ascender - _font.descender);
    for (NSInteger i = 0; i < count; i++) {
        CGPoint origin = origins[i];
        CTLineRef line = (__bridge CTLineRef)lines[i];
	//测试发现获取到的descend是大于0的
        CGFloat ascend,descend;
        CTLineGetTypographicBounds(line, &ascend, &descend, NULL);
        y = y - lineH;
        CGContextSetTextPosition(ctx, origin.x, y);
        CTLineDraw(line, ctx);
        
        y -= _lineSpace;
    }
   
    CFRelease(frame);
    CFRelease(setter);
}


六、后记

更多相关API用法,请参考官方API文档。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值