基于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/ 欢迎在本文下方评论,小方很需要鼓励支持!!! 本系列教程仅供学习交流 小说阅读器最终实现效果见 第一篇博文 前言 在上一篇文章中,我们实现 ...
随机推荐
- python3使用selenium3的坑
网络看了很多的文章,大部分都是不完整, 还有很多误导性极强的教程 ,特别是chromedriver这东西.简直一堆坑. 一首先是安装python3.6.5 root@ubuntu:~# add-apt ...
- ES6之用let,const和用var来声明变量的区别
var(掌握) 不区分变量和常量 用var声明的变量都是变量,都是可变的,我们可以随便对它进行运算操作.这样当多个人进行同一个项目时,区分变量和常量会越来越难,一不小心就会把设计为常量的数据更改了 ...
- 集成spring boot + mysql + docker实战
前言 网上找过很多文章,关于通过docker构建mysql容器并将应用容器和docker容器关联起来的文章不多.本文将给出具体的范例.此处为项目的源码 前置条件 该教程要求在宿主机上配置了: dock ...
- Matplotlib 绘图与可视化 一些属性和错误
属性 *)调整图像边缘及图像间的空白间隔plt.subplots.adjust(6个参数) 图像外部边缘的调整可以使用plt.tight_layout()进行自动控制,此方法不能够很好的控制图像间的间 ...
- [terry笔记]文件操作
如下记录一次作业: 很显然,我这个应该属于二逼青年版,会在以后更新文艺青年版的答案. 1.模仿sed,一个文件中,用新字符串替换老字符串. # file = input("file_name ...
- [AngularJS]Chapter 1 AnjularJS简介
创建一个完美的Web应用程序是很令人激动的,但是构建这样应用的复杂度也是不可思议的.我们Angular团队的目标就是去减轻构建这样AJAX应用的复杂度.在谷歌我们经历过各种复杂的应用创建工作比如:GM ...
- 疯狂Java学习笔记(70)-----------挚爱Java
与大家分享! 挚爱Java 10个使用Java最广泛的现实领域 写好Java代码的30条经验总结 Java字符串的substring真的会引起内存泄露么? Java内存的原型及工作原理深度剖析 Jav ...
- Process Monitor
https://en.wikipedia.org/wiki/Process_Monitor Process Monitor is a free tool from Windows Sysinterna ...
- CNN tflearn处理mnist图像识别代码解说——conv_2d参数解释,整个网络的训练,主要就是为了学那个卷积核啊。
官方参数解释: Convolution 2D tflearn.layers.conv.conv_2d (incoming, nb_filter, filter_size, strides=1, pad ...
- nyoj--1087--摆方格(规律)
摆方格 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 给你一个n*n的方格,每个方格里的数必须连续摆放如 1 2 4 3 ,下图为不连续的,请输出从左上角到右下角的对角 ...