基于Core Text实现的TXT电子书阅读器
本篇文章的项目地址基于Core Text实现的TXT电子书阅读器。
最近花了一点时间学习了iOS的底层文字处理的框架Core Text。在网上也参考很多资料,具体的资料在文章最后列了出来,有兴趣的可参考一下。
本篇主要介绍实现TXT电子书阅读器设计用到的Core Text相关的用法与实现。
关于Core Text
Core Text是iOS底层的文字处理框架,只提供一套C函数接口,使用Core Text对象时要注意手动管理内存以避免发生内存泄漏。之前写了一篇iOS富文本(二)初识Text Kit是介绍iOS的另一个文字处理框架Text Kit,Text Kit是封装Core Text函数提供一套Objective-C的接口,使用起来也比较友好。高度的封装意味着可定制性差,灵活性低。所以如果需要实现更多的功能最好还是用Core Text。
关于文字的相关知识参考文章最后列出的资料,因为涉及到字体大小的计算。例如在算字体高度时应该是baseline+ascent+descent的总和,了解这些知识对理解Core Text相关函数很有帮助
Core Text运行时的层次

介绍一下这个层级。framesetter对象(CTFramesetterRef)最为顶层接收一个属性化字符串(attributedString)作为输入,一个framesetter对象生成一个或多个文本中的帧(CTFrameRef)每一个CTFrame都代表一个段落。
要生成帧(CTFrameRef)时,framesetter调用一个typesetter对象(CTTypesetterRef),它放置文本在frame中,framesetter设置段落样式给typesetter对象,包括属性对齐方式,制表位,行间距,缩进和换行模式,typesetter对象用这些属性转换每个字符成字形,然后在每行中填充这些字型,再用这些行填满整个绘制区间。
每个CTFrame对象包含段落线(CTLine)对象。每个(CTLine)对象代表段落中的每一行,一个CTFrame可以包含一个或者多个CTLine对象。CTLine由typesetter对象操作期间被创建。
每个CTLine是包含字形管理(CTRun)对象的数组,一个CTRun对象是一组共享相同属性,方向的连续字形。
基于Core Text实现的电子书阅读器
根据配置文件得到文字显示的属性。
+(NSDictionary *)parserAttribute:(LSYReadConfig *)config
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSForegroundColorAttributeName] = config.fontColor;
dict[NSFontAttributeName] = [UIFont systemFontOfSize:config.fontSize];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = config.lineSpace;
paragraphStyle.alignment = NSTextAlignmentJustified;
dict[NSParagraphStyleAttributeName] = paragraphStyle;
return [dict copy];
}
根据属性生成属性化字符串,然后属性化字符串作为输入得到CTFrame对象
+(CTFrameRef)parserContent:(NSString *)content config:(LSYReadConfig *)parser bouds:(CGRect)bounds
{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:content];
NSDictionary *attribute = [self parserAttribute:parser];
[attributedString setAttributes:attribute range:NSMakeRange(0, content.length)];
CTFramesetterRef setterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attributedString);
CGPathRef pathRef = CGPathCreateWithRect(bounds, NULL);
CTFrameRef frameRef = CTFramesetterCreateFrame(setterRef, CFRangeMake(0, 0), pathRef, NULL);
CFRelease(setterRef);
CFRelease(pathRef);
return frameRef;
}
生成的CTFrame在要View的drawRect方法中调用CTFrameDraw就可以进行绘制。
因为我们不仅要绘制出文字还要和文字进行交互所以仅仅这两个函数是不够的。
还需要以下函数
//根据触摸点获取当前文字的索引
+(CFIndex)parserIndexWithPoint:(CGPoint)point frameRef:(CTFrameRef)frameRef
{
CFIndex index = -1;
CGPathRef pathRef = CTFrameGetPath(frameRef); //获取绘制的路径
CGRect bounds = CGPathGetBoundingBox(pathRef); //获取绘制的区间
NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frameRef); //获取绘制区间内所有的行数
if (!lines) {
return index;
}
NSInteger lineCount = [lines count];
CGPoint *origins = malloc(lineCount * sizeof(CGPoint)); //给每行的起始点开辟内存
if (lineCount) {
CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins); //获取每行的坐标
for (int i = 0; i<lineCount; i++) {
CGPoint baselineOrigin = origins[i];
CTLineRef line = (__bridge CTLineRef)[lines objectAtIndex:i];
CGFloat ascent,descent,linegap; //声明字体的上行高度和下行高度和行距
CGFloat lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &linegap); //获取每行的宽度
CGRect lineFrame = CGRectMake(baselineOrigin.x, CGRectGetHeight(bounds)-baselineOrigin.y-ascent, lineWidth, ascent+descent+linegap+[LSYReadConfig shareInstance].lineSpace); //没有转换坐标系左下角为坐标原点 字体高度为上行高度加下行高度
if (CGRectContainsPoint(lineFrame,point)){
index = CTLineGetStringIndexForPosition(line, point); //得到当前文字的索引
break;
}
}
}
free(origins); 释放内存
return index;
}
长按文字会默认选中两个文字这样就要计算选中的区间
+(CGRect)parserRectWithPoint:(CGPoint)point frameRef:(CTFrameRef)frameRef
{
CFIndex index = -1;
CGPathRef pathRef = CTFrameGetPath(frameRef);
CGRect bounds = CGPathGetBoundingBox(pathRef);
CGRect rect = CGRectZero;
NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frameRef);
if (!lines) {
return rect;
}
NSInteger lineCount = [lines count];
CGPoint *origins = malloc(lineCount * sizeof(CGPoint)); //给每行的起始点开辟内存
if (lineCount) {
CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins);
for (int i = 0; i<lineCount; i++) {
CGPoint baselineOrigin = origins[i];
CTLineRef line = (__bridge CTLineRef)[lines objectAtIndex:i];
CGFloat ascent,descent,linegap; //声明字体的上行高度和下行高度和行距
CGFloat lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &linegap);
CGRect lineFrame = CGRectMake(baselineOrigin.x, CGRectGetHeight(bounds)-baselineOrigin.y-ascent, lineWidth, ascent+descent+linegap+[LSYReadConfig shareInstance].lineSpace); //没有转换坐标系左下角为坐标原点 字体高度为上行高度加下行高度加行间距 注:[LSYReadConfig shareInstance].lineSpace为配置文件中设置的行间距
if (CGRectContainsPoint(lineFrame,point)){
CFRange stringRange = CTLineGetStringRange(line);
index = CTLineGetStringIndexForPosition(line, point);
CGFloat xStart = CTLineGetOffsetForStringIndex(line, index, NULL); //获取当前索引在当前行的偏移量
CGFloat xEnd;
//默认选中两个单位
if (index > stringRange.location+stringRange.length-2) {
xEnd = xStart;
xStart = CTLineGetOffsetForStringIndex(line,index-2,NULL);
}
else{
xEnd = CTLineGetOffsetForStringIndex(line,index+2,NULL);
}
rect = CGRectMake(origins[i].x+xStart,baselineOrigin.y-descent,fabs(xStart-xEnd), ascent+descent);
break;
}
}
}
free(origins);
return rect;
}
上面就是实现这个项目使用的大部分关于Core Text代码,实际项目实现起来远比这要复杂的多。具体实现请参考这个项目基于Core Text实现的TXT电子书阅读器
参考资料
Core Text 入门
基于 CoreText 的排版引擎:基础
Core Text Tutorial for iOS: Making a Magazine App
NIAttributedLabel.m
WFCoretext
基于Core Text实现的TXT电子书阅读器的更多相关文章
- 基于React实现的【绿色版电子书阅读器】,支持离线下载
代码地址如下:http://www.demodashi.com/demo/12052.html MyReader 绿色版电子书阅读器 在线地址:http://myreader.linxins.com ...
- 使用 Vue 和 epub.js 制作电子书阅读器
ePub 简介 ePub 是一种电子书的标准格式,平时我看的电子书大部分是这种格式.在手机上我一般用"多看"阅读 ePub 电子书,在 Windows 上找不到用起来比较顺心的软件 ...
- s3c2440 上txt 小说阅读器
文件结构 Makefile: CROSSCOMPILE := arm-linux- CFLAGS := -Wall -O2 -c LDFLAGS := -lm -lfreetype CC := $(C ...
- [翻译] Core Text Objective-C Wrapper
Core Text Objective-C Wrapper https://github.com/akosma/CoreTextWrapper Introduction(介绍) One of the ...
- 电子书及阅读器Demo
电子书阅读器(Kindle,电子纸技术.LCD.电子墨水技术等: 亚马逊/当当网站) 电子书产业可分5大环节:内容供应商.数字格式制作商.内容流通服务平台.传输平台以及终端阅读器产品. 全球电子书市 ...
- Android简单的编写一个txt阅读器(没有处理字符编码),适用于新手学习
本程序只是使用了一些基本的知识点编写了一个比较简单粗陋的txt文本阅读器,效率不高,只适合新手练习.所以大神勿喷. 其实想到编写这种程序源自本人之前喜欢看小说,而很多小说更新太慢,所以本人就只能找一个 ...
- 翻译:打造基于Sublime Text 3的全能python开发环境
原文地址:https://realpython.com/blog/python/setting-up-sublime-text-3-for-full-stack-python-development/ ...
- Core Text概述
本文是我翻译的苹果官方文档<Core Text Overview> Core Text框架是高级的底层文字布局和处理字体的技术.它在Mac OS X v10.5 and iOS 3.2开始 ...
- 【RecyclerView与Glide】实现一个Android电子书阅读APP
http://www.cnblogs.com/xfangs/ 欢迎在本文下方评论,小方很需要鼓励支持!!! 本系列教程仅供学习交流 小说阅读器最终实现效果见 第一篇博文 前言 在上一篇文章中,我们实现 ...
随机推荐
- [luogu4133 BJOI2012] 最多的方案 (计数dp)
题目描述 第二关和很出名的斐波那契数列有关,地球上的OIer都知道:F1=1, F2=2, Fi = Fi-1 + Fi-2,每一项都可以称为斐波那契数.现在给一个正整数N,它可以写成一些斐波那契数的 ...
- 关于libnmap 的一些应用
随笔描述 nmap 可以进行端口的扫描,在安全或运维中可以说是一款不错的神奇吧,在大部分LINUX 里面都自带了nmap 这款工具,他不仅仅是端口扫描,自身还提供许多插件可以使用. 官方文档 nmap ...
- ubuntu12.04开启Framebuffer
一.framebuffer概述 Framebuffer在Linux中是作为设备来实现的,它是对图形硬件的一种抽象,代表着显卡中的帧缓冲区(Framebuffer).通过Framebuffer设备,上层 ...
- python_三级字典
data = { "北京":{ "昌平":{ "沙河":["oldboy","test"], &qu ...
- nutch+hadoop 配置使用
nutch+hadoop 配置使用 配置nutch+hadoop 1,下载nutch.如果不需要特别开发hadoop,则不需要下载hadoop.因为nutch里面带了hadoop core包以及相关配 ...
- Maven多模块项目搭建
最近一直在思考如何能够更好的重用代码.减少重复劳动,之前有一篇文章通过导入JAR包的形式,可以重用部分形如util类的方法,但是这样的话,管理起来jar包,特别是协同工作,多项目情况下,管理JAR会出 ...
- 在IDEA中代码自动提示第一个字母大小写必须匹配的解决
在IDEA中代码自动提示第一个字母大小写必须匹配的解决 学习了:http://blog.csdn.net/babys/article/details/41775715 setting>Edito ...
- 使用默认system_health分析死锁(Deadlock)
在2008之前我们分析死锁须要用profiler trace或者trace flag 1222,1204.在2008中引入了一个新功能:Extended Events(扩展事件).能够监控Deadlo ...
- javaWeb web.xml 配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http:// ...
- 弹性ScrollView,和下啦刷新的效果相似 实现下拉弹回和上拉弹回
今天做了一个弹性ScrollView,和下啦刷新的效果类似,我想这个非常多需求都用的这样的效果 事实上这是一个自己定义的scrollView,上代码.这是我写在一个公共的组件包里的 package c ...