首先我是萌新,ios开发新手,一些大牛觉得不对,错误的,请勿喷我怎么吃螃蟹,请告诉我怎么吃就好了,谢谢了。
之前因为项目需求,要给小说阅读器中内容加上下划线,但是这个下划线又跟其他的不一样,这个下划线要在基线的位置再靠上一些,当初不明白为什么要这么做,而且效果也很难看,用户体验也不好,但是老大说这能防止pdf扫描vip章节内容,所以还是得老老实实的做,就对coreText开始了研究。
本次主要用到的点在与CTLineRef.接下来先对其做一些了解。
1.字符(Character)和字形(Glyphs)
排版系统中文本显示的一个重要的过程就是字符到字形的转换,字符是信息本身的元素,而字形是字符的图形表征,字符还会有其它表征比如发音。 字符在计算机中其实就是一个编码,某个字符集中的编码,比如Unicode字符集,就囊括了大都数存在的字符。 而字形则是图形,一般都存储在字体文件中,字形也有它的编码,也就是它在字体中的索引。 一个字符可以对应多个字形(不同的字体,或者同种字体的不同样式:粗体斜体等);多个字符也可能对应一个字形,比如字符的连写( 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的坐标系原点在左上角。
看完上边儿这些,该上代码勒。
先拿一个定义好的属性字符串。
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:self.text];//这个self.text 就是你要用到的字符串
[attrString setAttributes:self.coreTextAttributes range:NSMakeRange(0, attrString.length)];//这里的self.coreTextAttributes就是一个字典,来配置这个属性字符串的,可在他的set方法中随意设置,比如颜色,下划线,删除线,字型等等
然后把属性字符串放到frame中
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrString);
CGPathRef path = CGPathCreateWithRect(self.bounds, NULL);
if (_ctFrame != NULL) { //这里的_ctFrame 就是一个装有字符属性的集合
CFRelease(_ctFrame), _ctFrame = NULL;
}
_ctFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
CFRelease(path);
CFRelease(frameSetter);
做完了上面这些,就该开始画字了。
在- (void)drawRect:(CGRect)rect方法中
if (!_ctFrame) return;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CTFrameDraw(_ctFrame, context);
这样写的效果如图
是镜像过来的,要再翻过来
CGAffineTransform transform = CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height);
CGContextConcatCTM(context, transform);
加上以上这两句就可以了,就顺利的将字画到了画布上。
再之后给文字加个方框
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height));
CFArrayRef lines = CTFrameGetLines(_ctFrame);
CFIndex linecount = CFArrayGetCount(lines);
CGPoint origins[linecount];
CTFrameGetLineOrigins(_ctFrame, CFRangeMake(0, 0), origins);
NSInteger lineIndex = 0;
for (id oneLine in (__bridge NSArray *)lines) {
CGRect lineBounds = CTLineGetImageBounds((CTLineRef)oneLine, context);
lineBounds.origin.x += origins[lineIndex].x;
lineBounds.origin.y += origins[lineIndex].y;
lineIndex ++;
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 1.0);
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(context, poins, 4);
CGContextClosePath(context);
CGContextStrokePath(context);
}
}
其中主要是取CTFrameRef集合中的CTLines ,在其中他包含了字符的各种属性,rang等
第一次写文章,主要是当时搞这个东西走了弯路,对coreText的不了解,也在csdn和cocoachina上问了好多人,都没解决,防止以后忘记,自己再纪录一下。
我目前做的项目是做的小说阅读器,网上的素材真的是不多,现在也算是写的差不多了,但是在预加载,内存缓存和磁盘缓存上做的还是不行,希望有做阅读器这方面有好的方案的,希望可以教教小弟,第一篇小文 也就搞定勒。