之前一直很想好好的研究一下coreText,现在正好有这个机会,特此记录一下
CTFrame 作为一个整体的画布(Canvas),其中由行(CTLine)组成,而每行可以分为一个或多个小方块(CTRun)。
注意:你不需要自己创建CTRun,Core Text将根据NSAttributedString的属性来自动创建CTRun。每个CTRun对象对应不同的属性,正因此,你可以自由的控制字体、颜色、字间距等等信息。
通常处理步聚:
1.其实流程是这样的: 1、生成要绘制的NSAttributedString对象。 2、生成一个CTFramesetterRef对象,然后创建一个CGPath对象,这个Path对象用于表示可绘制区域坐标值、长宽。 3、使用上面生成的setter和path生成一个CTFrameRef对象,这个对象包含了这两个对象的信息(字体信息、坐标信息),它就可以使用CTFrameDraw方法绘制了。
其中可以更详细的设置换行方式,对齐方式,绘制区域的大小等。
2.绘制只是显示,点击事件就需要一个判断了。
CTFrame 包含了多个CTLine,并且可以得到各个line的其实位置与大小。判断点击处在不在某个line上。CTLine 又可以判断这个点(相对于ctline的坐标)处的文字范围。然后遍历这个string的所有NSTextCheckingResult,根据result的rang判断点击处在不在这个rang上,从而得到点击的链接与位置。
下面是一些相关的基本知识
字体的基本知识:
字体(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):组成字符的线或曲线。可以加粗或改变字符形状
衬线(Serif):用来使字符更可视的一条水平线。如字母左上角和下部的水平线。
无衬线(Sans Serif):可以让排字员不使用衬线装饰。
方形字(Block):这种字体的笔画使字符看起来比无衬线字更显眼,但还不到常见的衬线字的程度。例如Lubalin Graph就是方形字,这种字看起来好像是木头块刻的一样
手写体脚本(Calligraphic script):是一种仿效手写体的字体。例如Murray Hill或者Fraktur字体
艺术字(Decorative):像绘画般的字体
Pi符号(Pisymbol):非标准的字母数字字符的特殊符号。例如Wingdings和Mathematical Pi
连写(Ligature):是一系列连写字母如fi、fl、ffi或ffl。由于字些字母形状的原因经常被连写,故排字员已习惯将它们连写。
字符属性名称:
const CFStringRef kCTCharacterShapeAttributeName;
//字体形状属性 必须是CFNumberRef对象默认为0,非0则对应相应的字符形状定义,如1表示传统字符形状
const CFStringRef kCTFontAttributeName;
//字体属性 必须是CTFont对象
const CFStringRef kCTKernAttributeName;
//字符间隔属性 必须是CFNumberRef对象
const CFStringRef kCTLigatureAttributeName;
//设置是否使用连字属性,设置为0,表示不使用连字属性。标准的英文连字有FI,FL.默认值为1,既是使用标准连字。也就是当搜索到f时候,会把fl当成一个文字。必须是CFNumberRef 默认为1,可取0,1,2
const CFStringRef kCTForegroundColorAttributeName;
//字体颜色属性 必须是CGColor对象,默认为black
const CFStringRef kCTForegroundColorFromContextAttributeName;
//上下文的字体颜色属性 必须为CFBooleanRef 默认为False,
const CFStringRef kCTParagraphStyleAttributeName;
//段落样式属性 必须是CTParagraphStyle对象 默认为NIL
const CFStringRef kCTStrokeWidthAttributeName;
//笔画线条宽度 必须是CFNumberRef对象,默为0.0f,标准为3.0f
const CFStringRef kCTStrokeColorAttributeName;
//笔画的颜色属性 必须是CGColorRef 对象,默认为前景色
const CFStringRef kCTSuperscriptAttributeName;
//设置字体的上下标属性 必须是CFNumberRef对象 默认为0,可为-1为下标,1为上标,需要字体支持才行。如排列组合的样式Cn1
const CFStringRef kCTUnderlineColorAttributeName;
//字体下划线颜色属性 必须是CGColorRef对象,默认为前景色
const CFStringRef kCTUnderlineStyleAttributeName;
//字体下划线样式属性 必须是CFNumberRef对象,默为kCTUnderlineStyleNone 可以通过CTUnderlineStypleModifiers 进行修改下划线风格
const CFStringRef kCTVerticalFormsAttributeName;
//文字的字形方向属性 必须是CFBooleanRef 默认为false,false表示水平方向,true表示竖直方向
const CFStringRef kCTGlyphInfoAttributeName;
//字体信息属性 必须是CTGlyphInfo对象
const CFStringRef kCTRunDelegateAttributeName
//CTRun 委托属性 必须是CTRunDelegate对象
关于CTM
CTM,Context Translate Matrix。 它是把要绘制的上下文以一个叫做Matrix的东西来表示,可以简单地想作,绘制的上下文的每一个点都映射在Matrix上,你在Matrix上的操作都会使得上下文上的点产生相应的变动。如放大、旋转、移动。
在一般的教程里面,为了达到旋转或放大缩小的目的,一般都会先改变这个上下文,如:
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
然后进行绘图操作。那么这个绘图操作是怎么做的呢?这个对Matrix的操作,为什么是放在前面而不是放在后面,为什么放在后面又没有效果呢?不是说改变Matrix就会改变上面的映射的所有点呢?这些常规的逻辑思维使得问题越发无法理解和解决。那么我们先从context来了解。
一般情况也,我们总是认为context就是画布,所有的matrix旋转都是针对画布的旋转,虽然这样的理解是错误的,但是得到的结果却是正确的,但是如果在一些稍复杂的坐标系转换时,或者更改matrix时在之前或之后的理解时,这样理解就会得到难得理解的结果。
其实context说的是绘画人所处的角度上下文。如下图,默认的情况下,绘画人的角度是正对着画布的:
画布是白色的,而我则是在左上角用一个黄色的三角形来标识它的左上角,使用left top来标识context的左上角,而绘画人是黄色的圆形。
要记着!!画布无论怎么样都是正对着屏幕的,它不会旋转,或者放大缩小,或者移动。
那么为什么又看起来我放大了或者移动了呢?其实移动的是你的context,也就是你所处的context视角,我举个例子,比方说我要旋转180度在左上角写一个“abcdefg”。
首先,我要先旋转180度:
然后,我在左上角写上“abcdefg”:
然后重置context:
可以看到,我们改变context只是改变了自己面对画布的角度,而画布仍然是正对着屏幕的,自己始终以context的左上角为自己角度的左上角,而不是以画布的左上角为左上角,也就是说,这时绘画时的座标(0,0)是你旋转后context的left top,而不是画布 的左上角,记着这一点很重要。
所以,在绘画的时候,其实是倒着画在了画布的右下角上。而重置context,则是把自己正对着画布而已。这也就说清了为什么是在使用matrix更改context之后进行绘图有效(把自己面对画布的角度先调整了),而不是在画了之后再调整(因为你都画完了,再调整自己的角度还有什么用?)。
正确理解使用matrix更改context的方式很重要,因为这涉及到坐标系的问题,之后的CoreText相当讨论会讲到一个例子。
关于NS坐标系NS坐标系是以左下角为(0,0),与iOS的坐标系在Y上是相反的,所以,在iOS进行CoreText进行绘图或文字的时候,X方向是一致的,但是Y则是倒过来的。如下图:
那么怎么办呢?想想,仔细看上面这张图,貌似像是正常方向的倒影,但是水平线却在最上面。嗯,挪下来,然后再反过来,看一下效果。如下图:
效果:
效果果然如图所示,好!!
可是是不是就这样完了呢?不是,还有一个更为重要的问题,这个时候,进行了两次的转换matrix,context的left top在哪里呢?
根据之前的理论,那得让自己先把自己向下移,然后把头倒过来,OK,这下明白了,这下画布的左下角变成了context的左上角,别的都没变。这时,当你在(20, 20)画一个长方形,其实就是画布的(20, canvas.height - 20 + rect.size.height)的位置上画了个长方形,而且是倒过来的。
仔细想想这个,有趣的事情还有很多,因为按照自己看过本文之前的理论,可能会非常惊讶为什么得到的结果和自己想的不一样,一直以为是在(20, 20)处画一个长方形,结果却刚好相反,这就是没有理解context及matrix的正确含义所致。
参考文章: Study CoreText IOS CoreText.framework --- 基本用法