steps

CoreText简介(复制一段话吧):

CoreText是用于处理文字和字体的底层技术。它直接和Core Graphics(又被称为Quartz)打交道。Quartz是一个2D图形渲染引擎,能够处理OSX和iOS中图形显示问题。Quartz能够直接处理字体(font)和字形(glyphs),将文字渲染到界面上,它是基础库中唯一能够处理字形的模块。因此CoreText为了排版,需要将显示的文字内容、位置、字体、字形直接传递给Quartz。与其他UI组件相比,由于CoreText直接和Quartz来交互,所以它具有更高效的排版功能。

下面是CoreText的架构图,可以看到,CoreText处在非常底层的位置,上层的UI控件(包含UILable、UITextField及UITextView)和UIWebView都是基于CoreText来实现的。
盗一波图吧。。。


CoreText架构图

UIWebview也是处理复杂的文字排版的备选方案。对于排版,基于CoreText和基于UIWebView相比,具有以下不同点:

  • CoreText占用内存更少,渲染速度更快,UIWebView占用内存多,渲染速度慢。
  • CoreText在渲染界面前就可以精确地获得显示内容的高度(只要有了CTFrame即可),而UIWebView只有渲染出内容后,才能获得内容的高度(而且还需要通过JavaScript代码来获取)。
  • CoreText的CTFrame可以在后台线程渲染,UIWebView的内容只能在主线程(UI线程)渲染。
  • 基于CoreText可以做更好的原生交互效果,交互效果可以更细腻。而UIWebView的交互效果都是利用JavaScript来实现的,在交互效果上会有一些卡顿情况存在。例如,在UIWebView下,一个简单的按钮按下操作,都无法做出原生按钮的即时和细腻的按下效果。

当然,基于CoreText的排版方案也有那么一些劣势:

  • CoreText渲染出来的内容不能像UIWebView那样方便的支付内容的复制。
  • 基于CoreText来排版需要自己处理很多复杂逻辑,例如需要自己处理图片和文字混排相关的逻辑,也需要自己实现链接点击操作的支持。

在一次机缘巧合之下了解了CoreText这个东西,看是看了好多资料好多帖子都是模模糊糊的感觉,今天就写点笔记记录一下同时也加深一下印象。
首先呢排版不外乎文字和图片两种格式同事文字和图片再往深的处理也就是文字的显示格式及特殊文字的点击事件和图片的点击事件而已,所以下面就将从最简单的开始由浅入深的介绍一下。

首先介绍几点概念,如果看完感觉模模糊糊的请接着往下看,看完下面的再返回来看会有一种豁然开朗的感觉。

  • CoreText起初是为OSX设计的,而OSX得坐标原点是左下角,y轴正方向朝上。iOS中坐标原点是左上角,y轴正方向向下。若不进行坐标转换,则文字从下开始,还是倒着的
  • 下面要开始一波盗图了

在这你只要知道,一会我们绘制图片的时候实际上是在一个CTRun中绘制这个图片,那么CTRun绘制的坐标系中,他会以origin点作为原点进行绘制。基线为过原点的x轴,ascent即为CTRun顶线距基线的距离,descent即为底线距基线的距离

  • 又要盗一波图了
CTFrame组成
  • CTLine 可以看做Core Text绘制中的一行的对象 通过它可以获得当前行的line ascent,line descent ,line leading,还可以获得Line下的所有Glyph Runs
  • CTRun 或者叫做 Glyph Run,是一组共享相同attributes(属性)的字形的集合体

一个CTFrame有几个CTLine组成,有几行文字就有几行CTLine。一个CTLine有包含多个CTRun,一个CTRun是所有属性都相同的那部分富文本的绘制单元。所以CTRun是CTFrame的基本绘制单元

纯文字的绘制

1.获取绘图上下文

CGContextRef context = UIGraphicsGetCurrentContext();

2.翻转坐标系

CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.view.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

3.获取绘制区域

CGMutablePathRef path = CGPathCreatMutable();
CGPathAddRect(path, NULL, self.bounds);

4.设置绘制内容

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"这仅仅是一段文字"];

5.生成frame

CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
CTframeRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attributedString.length), path, NULL);

6.开始绘制

CTFrameDraw(frame, context);

7.释放资源

CFRelease(frame);
CFRelease(frameSetter);
CFRelease(path);

文字中插入图片

1.获取绘图上下文

CGContextRef context = UIGraphicsGetCurrentContext();

2.翻转坐标系

CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

3.获取绘制区域

CGMutablePathRef path = CGPathCreatMutable();
CGPathAddRect(path, NULL, self.bounds);

4.设置绘制内容

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"这仅仅是一段文字"];

5.为图片设置回调结构体

//设置一个回调结构体,告诉代理该回调那些方法
CTRunDelegateCallbacks callBacks;//创建一个回调结构体,设置相关参数
memset(&callBacks, 0, sizeof(CTRundelegateCallbacks));//memset将已开辟内存空间 callbacks 的首 n 个字节的值设为值 0, 相当于对CTRunDelegateCallbacks内存空间初始化
callBacks.version = KCTRunDelegateVersion1;//设置回调版本,默认这个
callBacks.getAscent = ascentCallBacks;//设置图片顶部距离基线的距离
callBacks.getDescent = descentCallBacks;//设置图片底部距离基线的距离
callBacks.getWidth = widthCallBacks;//设置图片宽度

6.设置代理

NSDictionary *dict = @{@"width":@100,@"height":@200};//创建一个图片尺寸的字典,初始化代理对象需要
CTRundelegateRef delegate = CTRunDelegateCreate(&callBacks, (__bridge void *)dict);//创建代理

上面只是设置了回调结构体,然而我们还没有告诉这个代理我们要的图片尺寸。所以这句话就在设置代理的时候绑定了一个返回图片尺寸的字典。事实上此处你可以绑定任意对象。此处你绑定的对象既是回调方法中的参数ref
将回调方法放到此处

static CGFloat ascentCallBacks(void * ref)
{
  return [(NSNumber*)[(__bridge NSDictionary*)ref valueForKey@"height"] floatValue];
}
static CGFloat descentCallBacks(void * ref)
{
  return 0;
}
static CGFloat widthCallBacks(void * ref)
{
  return [(NSNumber*)[(__bridge NSDictionary*)ref valueForKey@"width"] floatValue];
}

7.创建一个富文本类型的占位符绑定我们的代理

unichar placeHolder = 0xFFFC;//创建空白字符
NSString *palceHolderStr = [NSString stringWithCharacters:&placeHolder length:1];
NSMutableAttributedString *placeHolderAttStr = [[NSMutableAttributedString alloc] initWithString:palceHolderStr];
CFAttributedStringSetAttributed((CTAttributedStringRef)placeHolderAttStr, CFRangeMake(0, 1), KCTRunDelegateAttributedName, delegate);//给字符串中的范围字符串设置代理
CFRelease(delegate);//释放代理

8.将占位富文本插入到我们的富文本当中的某个位置

[attributedString insertAttributedSrting:placeHolderAttStr atIndex:3];

9.生成富文本的frame

CTFramesetterRef frameSetter = CTframeSetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedString.length), path, NULL);

10.开始绘制文字

CTFrameDraw(frame, context);

11.根据文本frame找到图片占位符的位置并返回图片绘制区域(这里面好多东西)

思路呢,就是遍历我们的frame中的所有CTRun,检查他是不是我们绑定图片的那个(依据呢就是我们给占位图片的CTRun绑定过代理),如果是,根据该CTRun所在CTLineorigin以及CTRunCTLine中的横向偏移量计算出CTRun的原点,加上其尺寸即为该CTRun的尺寸。

//---根据frame返回图片绘制的区域---
-(CGRect)handleActiveRectWithFrame:(CTFrameRef)frame {
  NSArray *arrLines = (NSArray *)CTFrameGetLines(frame); //根据frame获取CTLine数组
  NSInteger count = [arrLines count]; //线的数量
  CGPoint points[count]; //创建一个起点数组(CGPoint为结构体,故用C语言数组)
  CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points); //获取起点
  for (int i = 0; i < count; i++) {  //遍历线的数组
    CTLineRef line = (__bridge CTLineRef)arrLines[i];
    NSArray *arrRuns = (__bridge CTRunRef)CTLineGetGlyphRuns(line);
    for (int j = 0; j < arrRuns.count; j++) {  //遍历CTRun数组
      CTRunRef run = (__bridge CTRunRef)arrRuns[j]; 
      NSDictionary *dictionary = (NSDictionary *)CTRunGetAttributes(run); //获取CTRun的属性
      CTRunDekegateRef delegate = (__bridge CTRunDelegateRef)[dictionary valueForKey:(id)KCTRunDelegateAttributeName]; //获取CTRun绑定的代理
      CGPoint point = points[i]; //获取一个起点
      if (delegate == nil) {
        continue;
      }
      NSDictionary *dic = CTRunDelegateGetRefCon(delegate); //获取CTRunDelegate绑定的属性
      if (![dic iskindOfClass:[NSDictionary class]]) {
        continue;
      }
      return [self getLocWithFrame:frame CTLine:line CTRun:run origin:point];
    }
  }
  return CGRectZero;
}

-(CGRect)getLocWithFrame:(CTFrameRef)frame CTLine:(CTLineRef)line CTRun:(CTRunRef)run origin:(CGPoint)origin {
  CGFloat ascent; //获取上距
  CGFloat descent; //获取下距
  CGRect boundsRun; //创建一个frame
  boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
  boundsRun.size.height = ascent + descent;
  CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
  bounds.origin.x = origin.x + xOffset; ///point是行起点位置,加上每个字的偏移量得到每个字的x
  bounds.origin.y = origin.y - descent;
  CGPathRef path = CTFrameGetPath(frame); //获取绘制区域
  CGRect colRect = CGPathGetBoundingBox(path); //获取裁剪区域边框
  CGRect deleteBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y); //获取绘制区域
  return deleteBounds;
}

12.开始绘制图片

UIImage *image = [UIImage imageNamed:@"test_image"];
CGRect imgRect = [self handleActiveRectWithFrame:frame];
CGContextDrawImage(context, imgRect, image.CGimage);

13.释放资源

CFRelease(frame);
CFRelease(path);
CFRelease(frameSetter);

参考资料:
iOS:基于CoreText的排版引擎
CoreText实现图文混排

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • CoreText是一个进阶的比较底层的布局文本和处理字体的技术,CoreText API在OS X v10.5 和...
    smalldu阅读 13,041评论 18 128
  • iOS没有现成的支持图文混排的控件,而要用多个基础控件组合拼成图文混排这样复杂的排版,是件很苦逼的事情。对此的解决...
    清风沐沐阅读 644评论 0 2
  • blog.csdn.net CoreText实现图文混排 - 博客频道 CoreText实现图文混排 也好久没来写...
    K_Gopher阅读 564评论 0 0
  • 开场白 经常,即使我认为已经了解了什么,明白了什么,但只要与它面对面,就会马上意识到,之前其实可以算是对它一无所知...
    病没友阅读 1,566评论 0 1
  • 已收51
    海莲老师阅读 76评论 0 0