IOS CoreText.framework --- 行 CTLineRef
http://blog.csdn.net/fengsh998/article/details/8701738
前面两篇文章介绍了文字的样式,段落样式。本文章主要介绍行模式。CTLineRef
知识了解:
1.字符(Character)和字形(Glyphs)
排版系统中文本显示的一个重要的过程就是字符到字形的转换,字符是信息本身的元素,而字形是字符的图形表征,字符还会有其它表征比如发音。 字符在计算机中其实就是一个编码,某个字符集中的编码,比如Unicode字符集,就囊括了大都数存在的字符。 而字形则是图形,一般都存储在字体文件中,字形也有它的编码,也就是它在字体中的索引。 一个字符可以对应多个字形(不同的字体,或者同种字体的不同样式:粗体斜体等);多个字符也可能对应一个字形,比如字符的连写( Ligatures)。 
Roman Ligatures
下面就来详情看看字形的各个参数也就是所谓的字形度量Glyph Metrics


- bounding box(边界框 bbox),这是一个假想的框子,它尽可能紧密的装入字形。
- baseline(基线),一条假想的线,一行上的字形都以此线作为上下位置的参考,在这条线的左侧存在一个点叫做基线的原点,
- ascent(上行高度)从原点到字体中最高(这里的高深都是以基线为参照线的)的字形的顶部的距离,ascent是一个正值
- descent(下行高度)从原点到字体中最深的字形底部的距离,descent是一个负值(比如一个字体原点到最深的字形的底部的距离为2,那么descent就为-2)
- linegap(行距),linegap也可以称作leading(其实准确点讲应该叫做External leading),行高lineHeight则可以通过 ascent + |descent| + linegap 来计算。
一些Metrics专业知识还可以参考Free Type的文档 Glyph metrics,其实iOS就是使用Free Type库来进行字体渲染的。
以上图片和部分概念来自苹果文档 Querying Font Metrics ,Text Layout
2.坐标系
首先不得不说 苹果编程中的坐标系花样百出,经常让开发者措手不及。 传统的Mac中的坐标系的原点在左下角,比如NSView默认的坐标系,原点就在左下角。但Mac中有些View为了其实现的便捷将原点变换到左上角,像NSTableView的坐标系坐标原点就在左上角。iOS UIKit的UIView的坐标系原点在左上角。
往底层看,Core Graphics的context使用的坐标系的原点是在左下角。而在iOS中的底层界面绘制就是通过Core Graphics进行的,那么坐标系列是如何变换的呢? 在UIView的drawRect方法中我们可以通过UIGraphicsGetCurrentContext()来获得当前的Graphics Context。drawRect方法在被调用前,这个Graphics Context被创建和配置好,你只管使用便是。如果你细心,通过CGContextGetCTM(CGContextRef c)可以看到其返回的值并不是CGAffineTransformIdentity,通过打印出来看到值为
Printing description of contextCTM:
(CGAffineTransform) contextCTM = {
a = 1
b = 0
c = 0
d = -1
tx = 0
ty = 460
}
这是非retina分辨率下的结果,如果是如果是retina上面的a,d,ty的值将会乘2,如果是iPhone 5,ty的值会再大些。 但是作用都是一样的就是将上下文空间坐标系进行了flip,使得原本左下角原点变到左上角,y轴正方向也变换成向下。
还是老样子,拿一个事先定义好的属性字串进行开讲。
- NSString *src = [NSString stringWithString:@"其实流程是这样的: 1、生成要绘制的NSAttributedString对象。 "];
- NSMutableAttributedString * mabstring = [[NSMutableAttributedString alloc]initWithString:src];
- long slen = [mabstring length];
将属性字串放到frame当中。
- CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mabstring);
- CGMutablePathRef Path = CGPathCreateMutable();
- //坐标点在左下角
- CGPathAddRect(Path, NULL ,CGRectMake(10 , 10 ,self.bounds.size.width-20 , self.bounds.size.height-20));
- CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), Path, NULL);
显示效果:

得到属性字串在frame中被自动分成了多少个行。每行中有多少个CTRun
- //得到frame中的行数组
- CFArrayRef rows = CTFrameGetLines(frame);
- int rowcount = CFArrayGetCount(rows);
- NSLog(@"rowcount = %i",rowcount);
- CTLineRef line = CFArrayGetValueAtIndex(rows, 0);
- //从一行中得到CTRun数组
- CFArrayRef runs = CTLineGetGlyphRuns(line);
- int runcount = CFArrayGetCount(runs);
- NSLog(@"runcount = %i",runcount);
结果:
- 2013-03-20 23:07:38.835 CTextDemo[5612:207] rowcount = 2
- 2013-03-20 23:07:38.838 CTextDemo[5612:207] runcount = 17
将第一行设置为使用省略号模式
- NSAttributedString *truncatedString = [[NSAttributedString alloc]initWithString:@"\u2026"];
- CTLineRef token = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)truncatedString);
- CTLineTruncationType ltt = kCTLineTruncationStart;//kCTLineTruncationEnd;
- CTLineRef newline = CTLineCreateTruncatedLine(line, self.bounds.size.width-200, ltt, token);
CTLineDraw(newline, context);
效果:

CTLineTruncationType 为kCTLineTrunceationEnd;

省略号在中间

CFIndex CTLineGetGlyphCount( CTLineRef line );
获取一行中的图像个数,即有多少个CTRun。
CFArrayRef CTLineGetGlyphRuns( CTLineRef line );
获取CTRUN数组,可以通过CFArrayGetCount得到数组的个数得到的值与CTLineGetGlyphCount相同。
CGFloat CTLineGetOffsetForStringIndex( CTLineRef line, CFIndex charIndex, CGFloat* secondaryOffset );
获取一行文字中,指定charIndex字符相对x原点的偏移量,返回值与secondaryOffset同为一个值。如果charIndex超出一行的字符长度则反回最大长度结束位置的偏移量,如一行文字共有17个字符,哪么返回的是第18个字符的起始偏移,即第17个偏移+第17个字符占有的宽度=第18个起始位置的偏移。因此想求一行字符所占的像素长度时,就可以使用此函数,将charIndex设置为大于字符长度即可。
- //获取整段文字中charIndex位置的字符相对line的原点的x值
- CGFloat offset;
- CGFloat retoffset = CTLineGetOffsetForStringIndex(line,1,&offset);
- NSLog(@"return offset = %f",retoffset);
- NSLog(@"output offset = %f",offset);
效果:
- 2013-03-21 13:37:22.330 CTextDemo[6851:207] return offset = 12.000000
- 2013-03-21 13:37:22.331 CTextDemo[6851:207] output offset = 12.000000

double CTLineGetPenOffsetForFlush( CTLineRef line, CGFloat flushFactor, double flushWidth );
获取相对于Flush的偏移量。即[flushwidth - line(字符占的像素)]*flushFactor/100;这是我个人推的公式,发现精确度上还存在偏差。
当flushFactor取值为0,0.5,1时分别显示的效果为左对齐,居中对齐,右对齐。
演示代码:
- - (void)drawRect:(CGRect)rect
- {
- NSString *src = [NSString stringWithString:@"其实流程是这样的: 1、生成要绘制的NSAttributedString对象。 "];
- NSMutableAttributedString * mabstring = [[NSMutableAttributedString alloc]initWithString:src];
- long slen = [mabstring length];
- CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mabstring);
- CGMutablePathRef Path = CGPathCreateMutable();
- //坐标点在左下角
- CGPathAddRect(Path, NULL ,CGRectMake(10 , 10 ,self.bounds.size.width-20 , self.bounds.size.height-20));
- CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), Path, NULL);
- //得到frame中的行数组
- CFArrayRef rows = CTFrameGetLines(frame);
- if (rows) {
- const CFIndex numberOfLines = CFArrayGetCount(rows);
- const CGFloat fontLineHeight = [UIFont systemFontOfSize:20].lineHeight;
- CGFloat textOffset = 0;
- CGContextRef ctx = UIGraphicsGetCurrentContext();
- CGContextSaveGState(ctx);
- CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y+[UIFont systemFontOfSize:20].ascender);
- CGContextSetTextMatrix(ctx, CGAffineTransformMakeScale(1,-1));
- for (CFIndex lineNumber=0; lineNumber<numberOfLines; lineNumber++) {
- CTLineRef line = CFArrayGetValueAtIndex(rows, lineNumber);
- float flush;
- switch (2) {
- case UITextAlignmentCenter: flush = 0.5; break; //1
- case UITextAlignmentRight: flush = 1; break; //2
- case UITextAlignmentLeft: //0
- default: flush = 0; break;
- }
- CGFloat penOffset = CTLineGetPenOffsetForFlush(line, flush, rect.size.width);
- NSLog(@"penOffset = %f",penOffset);
- CGContextSetTextPosition(ctx, penOffset, textOffset);//在偏移量x,y上打印
- CTLineDraw(line, ctx);//draw 行文字
- textOffset += fontLineHeight;
- }
- CGContextRestoreGState(ctx);
- }
- }
效果:

CFIndex CTLineGetStringIndexForPosition( CTLineRef line, CGPoint position );
获取一行中光标点击处(position)的字符索引,这个值只能为0或最大字符长度。
CFRange CTLineGetStringRange( CTLineRef line );
获取一行字符占的范围(包括换行符一起计算),返回一行位置的起始位置(location)和长度(length)。
location不是每行都从0开始的,而是该行的前N行字符和。
double CTLineGetTrailingWhitespaceWidth( CTLineRef line );
获取一行未尾字符后空格的像素长度。如果:"abc "后面有两个空格,返回的就是这两个空格占有的像素长度。
- double wspace = CTLineGetTrailingWhitespaceWidth(line);
- NSLog(@"whitespacewidth = %f",wspace);
double CTLineGetTypographicBounds( CTLineRef line, CGFloat* ascent, CGFloat* descent, CGFloat* leading );
获取一行中上行高(ascent),下行高(descent),行距(leading),整行高为(ascent+|descent|+leading) 返回值为整行字符串长度占有的像素宽度。
- CGFloat asc,des,lead;
- double lineHeight = CTLineGetTypographicBounds(line, &asc, &des, &lead);
- NSLog(@"ascent = %f,descent = %f,leading = %f,lineheight = %f",asc,des,lead,lineHeight);
CGRect CTLineGetImageBounds( CTLineRef line, CGContextRef context );
获取一行文字的范围,什么意思,就是指把这一行文字点有的像素距阵作为一个image图片,来得到整个矩形区域。
演示代码:
- -(void)drawBounds
- {
- NSString *src = [NSString stringWithString:@"其实流程是这样的: 1、生成要绘制的NSAttributedString对象。 "];
- NSAttributedString * string = [[NSAttributedString alloc]initWithString:src];
- CGContextRef ctx = UIGraphicsGetCurrentContext();
- CGContextSetTextMatrix(ctx , CGAffineTransformIdentity);
- //CGContextSaveGState(ctx);
- //x,y轴方向移动
- CGContextTranslateCTM(ctx , 0 ,self.bounds.size.height);
- //缩放x,y轴方向缩放,-1.0为反向1.0倍,坐标系转换,沿x轴翻转180度
- CGContextScaleCTM(ctx, 1.0 ,-1.0);
- // layout master
- CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(
- (CFAttributedStringRef)string);
- CGMutablePathRef Path = CGPathCreateMutable();
- //坐标点在左下角
- CGPathAddRect(Path, NULL ,CGRectMake(0 , 0 ,self.bounds.size.width , self.bounds.size.height));
- CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), Path, NULL);
- CFArrayRef Lines = CTFrameGetLines(frame);
- int linecount = CFArrayGetCount(Lines);
- CGPoint origins[linecount];
- CTFrameGetLineOrigins(frame,
- CFRangeMake(0, 0), origins);
- NSInteger lineIndex = 0;
- for (id oneLine in (NSArray *)Lines)
- {
- CGRect lineBounds = CTLineGetImageBounds((CTLineRef)oneLine, ctx);
- lineBounds.origin.x += origins[lineIndex].x;
- lineBounds.origin.y += origins[lineIndex].y;
- lineIndex++;
- //画长方形
- //设置颜色,仅填充4条边
- CGContextSetStrokeColorWithColor(ctx, [[UIColor redColor] CGColor]);
- //设置线宽为1
- CGContextSetLineWidth(ctx, 1.0);
- //设置长方形4个顶点
- CGPoint poins[] = {CGPointMake(lineBounds.origin.x, lineBounds.origin.y),CGPointMake(lineBounds.origin.x+lineBounds.size.width, lineBounds.origin.y),CGPointMake(lineBounds.origin.x+lineBounds.size.width, lineBounds.origin.y+lineBounds.size.height),CGPointMake(lineBounds.origin.x, lineBounds.origin.y+lineBounds.size.height)};
- CGContextAddLines(ctx,poins,4);
- CGContextClosePath(ctx);
- CGContextStrokePath(ctx);
- }
- CTFrameDraw(frame,ctx);
- CGPathRelease(Path);
- CFRelease(framesetter);
- }
效果图:

通这个RECT我们可以对文字增加点击事件或其它触发动作等。
OK,CTLine 介绍完毕。
IOS CoreText.framework --- 行 CTLineRef的更多相关文章
- iOS xcode9 framework静态库的创建以及xib和图片的使用记录
来到了新公司,要开发的第一个项目据说可能要封成framework,可是我从来没自己做过framework呀!顿时开始发愤图强,赶紧恶补了起来.但是还是遇到了一些乱七八糟的情况,所以写个随笔记下来. 1 ...
- ios label 自动计算行高详解
在OC当中自动计算行高主要调用系统的 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff } span ...
- iOS在线更新framework,使用NSBundle动态读取
官方文档:https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/LoadingCode/Tasks/Loadin ...
- iOS Photos.framework框架
链接: iOS8.0 使用Photos.framework对相册的常用操作 iOS AssetsLibrary和Photos的使用总结: 权限及相册的获取 iOS 开发之照片框架详解 iOS Asse ...
- iOS 引入framework的常见问题和原理
今天在引入第三方framework时,我按照以前的方法,把framework加入到了下图的地方: 默认是required的,之后程序就crash了,报错dyld: Library not loaded ...
- iOS 创建framework & bundle 主要配置
bundle:base sdk 为iOS, delete compile resource framework:target dependencies,headers,mach-o proj: tar ...
- iOS 利用 Framework 进行动态更新
http://nixwang.com/2015/11/09/ios-dynamic-update/ 前言 目前 iOS 上的动态更新方案主要有以下 4 种: HTML 5 lua(wax)hotpat ...
- ios统计代码行数
要统计ios开发代码,包括头文件的,终端命令进入项目目录下,命令如下 列出每个文件的行数: find . -name "*.m" -or -name "*.h" ...
- iOS XMPP Framework 中文概述
本篇文章翻译XMPP Framework中的Overview of the XMPP Framework部分 介绍 The framework is divided into 2 parts: 1. ...
随机推荐
- Linux字符界面下用户账户的设置
在Linux系统字符界面下创建.修改以及删除用户账户主要使用useradd,usermod和userdel这3个命令. 一.创建用户账户 创建用户账户就是在系统中创建一个新账户,然后为新账户分配用户U ...
- 树莓派_360wifi2_佳能MP236打印机
入手树莓派后一直没时间弄,设想用360wifi做无线网卡,也一直不得解,今天成功写下经验 本人刷的是官方系统,版本为3.12.28,首先更新系统内核,参考以下网址: http://groenholdt ...
- eclipse报错 :java was started but returned exit code=13
一个礼拜没打开自己电脑上的eclipse,一打开居然报错,错误提示如下: cdm运行一下Java -version和javac 运行javac时报错,错误提示如下: 然后发现jdk的bin下没有dt. ...
- Entity Framwork(EF) 7——在现在数据库的甚而上开发MVC 新项目
一.开发背景: 由于老系统已经无法满足实际业务需求,需在现有数据库的甚而上开发新的项目. 二.困难点: 而EF默认情况下是要删除现有数据库表格后重新创建,这是不允许的.当你创建数据库对象时系统会提示“ ...
- Linux:去除认证,加速 SSH登录
编辑配置文件 /etc/ssh/sshd_config vim /etc/ssh/sshd_config 找到 UseDNS选项,如果没有注释,将其注释 #UseDNS yes 添加 UseDNS n ...
- Extract QQ from iPhone and analyze it
QQ is one of the most popular chat App in the world. Now let me show you how to extract QQ from iPho ...
- centos7.2上实践cgoup
基本介绍 CGroups 是一种对进程资源管理和控制的统一框架,它提供的是一种机制,而具体的策略(Policy)是通过子系统(subsystem)来完成的.子系统是CGroups对进程组进行资源控制的 ...
- win10开始菜单打不开怎么办 win菜单键没反应解决办法
win10开始菜单打不开怎么办 win菜单键没反应解决办法 —————————————————————————————————————————————————————————————————————— ...
- ios block和函数的区别
block是封装了一段代码的OC对象,可以被设为Property, 在调用block的地方block都会被替换成相应的代码,相当于内联函数. 函数可以使代码更加整洁易读,使用block会使代码可读性变 ...
- MySQL 5.7 深度解析: 半同步复制技术
复制架构衍生史 在谈这个特性之前,我们先来看看MySQL的复制架构衍生史. MySQL的复制分为四种: 普通的replication,异步同步. 搭建简单,使用非常广泛,从mysql诞生之初,就产生了 ...