iOS图形处理相关iOS技术点iOS开发成长之路

CoreText(图文混排)详解+Demo

2016-04-25  本文已影响7553人  和之一

      写这篇文章之前,在网上找了不少资料,也看了不少大牛们的文章;在这结合我自己对CoreText的理解,今晚整理出来分享给爱学习的你们,因为CoreText比较复杂所以需要用大量文字来讲解,最后有Demo。

一、CoreText简介

   CoreText是用于处理文字和字体的底层技术。它直接和Core Graphics(又被称为Quartz)打交道。Quart是一个2D图形渲染引擎,能够处理OSX和iOS中的图形显示。

Quartz能够直接处理字体(font)和字形(glyphs),将文字渲染到界面上,它是基础库中能够唯一处理字形的模块。因此,CoreText为了排版,需要将显示的文本内容、位置、字体、字形直接传递给Quartz。相比其他UI组件,由于CoreText直接和Quartz来交互,所以它具有高速的排版效果。

下图是CoreText的架构图,可以看到,CoreText处于非常底层的位置,上层的UI控件(包括UILabel,UITextField,以及UITextView)和UIWebView都是基于CoreText来实现的。

注意:这个是iOS7之后的架构图,在iOS7之前,并没有图中的Text Kit类,不过CoreText仍然是处在最底层直接和Core Graphics打交道的模块。

UIWebView也是处理复杂的文字排版的备选方案。对于排版,基于CoreText和基于UIWebView相比,前者有以下好处:

1>CoreText占用内存少,渲染速度快,UIWebView占用内存多,渲染速度慢。

2>CoreText在渲染界面前就可以精确地获得显示内容的高度(只要有了CTFrame即可),而UIWebView只有渲染出内容后,才能获得内容的高度(而且还需要用javascript代码来获取)

3>CoreText的CTFrame可以在后台线程渲染,UIWebView的内容只能在主线程渲染。

4>基于CoreText可以做更好的原生交互效果,交互效果可以更细腻。而UIWebView的交互效果都是用javascript来实现的,在交互效果上会有一些卡顿存在。

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

1>CoreText渲染出来的内容不能像UIWebView那样方便地支持内容复制。

2>CoreText需要自己处理很多复杂逻辑,例如需要自己处理图片和文字混排相关的逻辑,也需要自己实现链接点击操作的支持。

二、文字排版的基础概念

字体(Font):和我们平时说的字体不同,计算机意义上的字体表示的是同一大小,同一样式(Style)字形的集合。从这个意义上来说,当我们为文字设置粗体,斜体时其实是使用了另外一种字体(下划线不算)。

字符(Character)和字形(Glyphs):排版过程中一个重要的步骤就是从字符到字形的转换,字符表示信息本身,而字形是它的图形表现形式。字符一般就是指某种编码,如Unicode编码,而字形则是这些编码对应的图片。但是他们之间不是一一对应关系,同个字符的不同字体族,不同字体大小,不同字体样式都对应了不同的字形。而由于连写(Ligatures)的存在,多个字符也会存在对应一个字形的情况。

字形描述集(Glyphs Metris):即字形的各个参数。

如下面的两张图:

边框(Bounding Box):一个假想的边框,尽可能地容纳整个字形。

基线(Baseline):一条假想的参照线,以此为基础进行字形的渲染。

基础原点(Origin):基线上最左侧的点。

行间距(Leading):行与行之间的间距。

字间距(Kerning):字与字之间的距离,为了排版的美观,并不是所有的字形之间的距离都是一致的,但是这个基本不影响到我们的文字排版。

上行高度(Ascent)和下行高度(Decent):一个字形最高点和最低点到基线的距离,前者为正数,而后者为负数。当同一行内有不同的字体文字时,就取最大的值为相应的值。

如下图:

红框高度既为当前行的行高,绿线为baseline,绿色到红框上部分为当前行的最大Ascent,绿线到黄线为当前行的最大Decent,而黄框的高为行间距。

由此可以得出:lineHeight = Ascent + |Decent| + Leading

三、CoreText

iOS/OSX中用于描述富文本的类是NSAttributedString,顾名思义,它比NSString多了Attribute的概念,它可以包含很多属性,粗体,斜体,下划线,颜色,背景色等等,每个属性都有对应的字符区域。在OSX上我们只需解析完毕相应的数据,准备好NSAttributedString即可,底层的绘制完全可以交给相应的控件完成。但是在iOS上就没有这么方便,想要绘制Attributed String就需要用到CoreText了。(当然iOS6之后已经有AttributedLabel了)。

使用CoreText进行NSAttributedString的绘制,最重要的两个概念就是CTFrameSetter和CTFrame。他们的关系如下

其中CTFramesetter是由CFAttributedString(NSAttributedString)初始化而来,可以认为它是CTFrame的一个,通过传入CGPath生成相应的CTFrame并使用它进行渲染:直接以CTFrame为参数使用CTFrameDraw绘制或者从CTFrame中获取CTLine进行微调后使用CTLineDraw进行绘制。

一个CTFrame是由一行一行的Cline的组成,每个CTLine又包含若干个CTRun(既字形绘制的最小单元),通过相应的方法可以获取到不同位置的CTRun和CTLine,以实现对不同位置touch时间响应。

四、图文混排的实现

CoreText实际上并没有相应API直接将一个图片转换为CTRun并进行绘制,它所能做的只是为图片预留响应的空白区域,而真正的绘制则是交由CoreGraphics完成。

在CoreText中提供了CTRunDelegate这么个CoreFoundation类,顾名思义它可以对CTRun进行拓展。AttributedString某个段设置kCTRunDelegateAttributeName属性之后,CoreText使用它生成CTRun是通过当前Delegate的回调来获取自己的ascent,descent和width,而不是根据文字信息。这样就给我们留下了可操作的空间:用一个空白字符作为图片的占位符,设好Delegate,占好位置,然后用CoreGraphics进行图片的绘制。

Demo下载地址:https://github.com/TheYiOS/ZYCoreText

Demo看完之后回顾一下:

1、翻转坐标系

2、获取CTFrameRef

a.通过属性字符串获取CTFramesetterRef

b.设置一个CGPath

c.CTFramesetterRef+CGPath+CFRange(0, 0) 获取到CTFrameRef

3.通过FrameRef绘制内容

CTFrameRef包含若干个CTLineRef,每一个CTLineRef中包含若干个CTRunRef

字体:

文字的高度  height = Ascent + Decent

1.上行高(Ascent)

2.下行高 (Decent)

2.基线(Baseline)确定文本显示的位置

绘制图片:

1.先找到图片位置 (正则)

2.用一个空的属性字串替换 “图片名字”字符串

3.使用CTRunDelegateRef来设置图片的上行高、下行高和宽度,然后把CTRunDelegateRef设置为占位属性字符串上面的一个属性

->这样就完成了图片的占位,占位的大小就是图片显示的大小

4.绘制图片的时候,先遍历每一行(CTLineRef)的CTRunRef,查找当前的CTRunRef是否存在CTRunDelegateRef,如果存在就直接绘制图片

过两天会整理一下runtime,各位记得关注奥,你们的关注就是我的动力。

上一篇下一篇

猜你喜欢

热点阅读