基于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/ 欢迎在本文下方评论,小方很需要鼓励支持!!! 本系列教程仅供学习交流 小说阅读器最终实现效果见 第一篇博文 前言 在上一篇文章中,我们实现 ...
随机推荐
- 1、使用Python3爬取美女图片-网站中的每日更新一栏
此代码是根据网络上其他人的代码优化而成的, 环境准备: pip install lxml pip install bs4 pip install urllib #!/usr/bin/env pytho ...
- UNIX系统高级编程——第四章-文件和目录-总结
文件系统: 以UNIX系统V文件系统为例: 磁盘分为区,每个分区都有自己的文件系统: i节点是固定长度的记录项,包含了文件的相关信息.目录项包含文件名和i节点号.stat结构中除文件名和i节点编号 ...
- 递归树处理,配合vue的vueTreeselect组件使用
在项目中经常会使用到tree,并且需要对递归树进行操作. 在vue项目中,使用vue-treeselect插件(https://vue-treeselect.js.org/) 使用中遇到的问题: 1. ...
- (转)Epoll模型详解
1. 内核中提高I/O性能的新方法epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll.要使用epoll只需要这三个系统调 用:epoll_create(2), ...
- CentOS 安装SVNclient
1.检查是已经安装了svn: # rpm -qa subversion subversion-1.7.14-6.el7.x86_64 卸载旧版本号的svn: # yum remove subversi ...
- Android中Handler原理
Handler主要是主线程和子线程通信.一般子线程中做一些耗时操作做完之后通知主线程来改动UI. 实际上android系统在Activity启动或者状态变化等都是通过Handler机制实现的. 首先进 ...
- [LeetCOde][Java] Best Time to Buy and Sell Stock III
题目: Say you have an array for which the ith element is the price of a given stock on day i. Design a ...
- 【翻译自mos文章】怎么正确的计算一个ip地址的subnet id?
怎么正确的计算一个ip地址的subnet id? 来源于: How to calculate the correct subnet for an interface (文档 ID 1059759.1) ...
- RAC连接时的2种方式Connect Time Failver和taf
1. Client-side Connect Time Failover 在客户端的tnsname中配置多个地址,当用户连接时会按照次序尝试各个地址,直到连接成功,连接好后,不再检测地址是否可用,如 ...
- window如何在命令行关掉端口
欢迎加入前端交流群来py:749539640 进入cmd netstat -ano | findstr :yourPortNumber taskkill /PID typeyourPIDhere ...