本篇文章的项目地址基于Core Text实现的TXT电子书阅读器

最近花了一点时间学习了iOS的底层文字处理的框架Core Text。在网上也参考很多资料,具体的资料在文章最后列了出来,有兴趣的可参考一下。

本篇主要介绍实现TXT电子书阅读器设计用到的Core Text相关的用法与实现。

关于Core Text

Core TextiOS底层的文字处理框架,只提供一套C函数接口,使用Core Text对象时要注意手动管理内存以避免发生内存泄漏。之前写了一篇iOS富文本(二)初识Text Kit是介绍iOS的另一个文字处理框架Text KitText 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电子书阅读器的更多相关文章

  1. 基于React实现的【绿色版电子书阅读器】,支持离线下载

    代码地址如下:http://www.demodashi.com/demo/12052.html MyReader 绿色版电子书阅读器 在线地址:http://myreader.linxins.com ...

  2. 使用 Vue 和 epub.js 制作电子书阅读器

    ePub 简介 ePub 是一种电子书的标准格式,平时我看的电子书大部分是这种格式.在手机上我一般用"多看"阅读 ePub 电子书,在 Windows 上找不到用起来比较顺心的软件 ...

  3. s3c2440 上txt 小说阅读器

    文件结构 Makefile: CROSSCOMPILE := arm-linux- CFLAGS := -Wall -O2 -c LDFLAGS := -lm -lfreetype CC := $(C ...

  4. [翻译] Core Text Objective-C Wrapper

    Core Text Objective-C Wrapper https://github.com/akosma/CoreTextWrapper Introduction(介绍) One of the ...

  5. 电子书及阅读器Demo

    电子书阅读器(Kindle,电子纸技术.LCD.电子墨水技术等: 亚马逊/当当网站)  电子书产业可分5大环节:内容供应商.数字格式制作商.内容流通服务平台.传输平台以及终端阅读器产品. 全球电子书市 ...

  6. Android简单的编写一个txt阅读器(没有处理字符编码),适用于新手学习

    本程序只是使用了一些基本的知识点编写了一个比较简单粗陋的txt文本阅读器,效率不高,只适合新手练习.所以大神勿喷. 其实想到编写这种程序源自本人之前喜欢看小说,而很多小说更新太慢,所以本人就只能找一个 ...

  7. 翻译:打造基于Sublime Text 3的全能python开发环境

    原文地址:https://realpython.com/blog/python/setting-up-sublime-text-3-for-full-stack-python-development/ ...

  8. Core Text概述

    本文是我翻译的苹果官方文档<Core Text Overview> Core Text框架是高级的底层文字布局和处理字体的技术.它在Mac OS X v10.5 and iOS 3.2开始 ...

  9. 【RecyclerView与Glide】实现一个Android电子书阅读APP

    http://www.cnblogs.com/xfangs/ 欢迎在本文下方评论,小方很需要鼓励支持!!! 本系列教程仅供学习交流 小说阅读器最终实现效果见 第一篇博文 前言 在上一篇文章中,我们实现 ...

随机推荐

  1. 笔记本安装Archlinux笔记

    同步更新于wendster大佬的个人博客 搬运自我的洛谷博客 可能会不定期更新! 因为前几天给我的小炸鸡加了一根内存条:而且先前装的Xubuntu是32位的,使用极其不方便:再加上wendster大佬 ...

  2. 让前端攻城师独立于后端进行开发: Mock.js

    一.Mock.js是什么? 目前的大部分公司的项目都是采用的前后端分离, 后端接口的开发和前端人员是同时进行的. 那么这个时候就会存在一个问题, 在页面需要使用大量数据进行渲染生成前, 后端开发人员的 ...

  3. tp框架引入第三方sdk的经验总结

    tp框架开发常用到第三方的接口,这时候需要引入第三方的sdk.例如:微信扫码支付sdk,阿里大于的淘宝sdk等等 首先到官网上下载对应php的sdk文件,通常会有至少一个实例代码. 1 新建一个控制器 ...

  4. C++ throw的实验 & 异常类继承关系

    如果定义了 throw() 表示函数不抛出异常,这时候如果还是抛出,会导致运行时错误. #include <iostream> #include <exception> #in ...

  5. HDU 4165

    一块药看成括号配对就行了.很明显的直接求卡特兰数. 今晚看了HDU 3240的题,有一点思路,但无情的TLE.想不到什么好方法了,看了别人的解答,哇...简直是天才的做法啊....留到星期六自己思考一 ...

  6. xcode5.1生成framework,支持arm64报错

    错误例如以下: ld: Assertion failed: (_machoSection != 0), function machoSection, file /SourceCache/ld64/ld ...

  7. 【c语言】输入一个递增排序的数组的一个旋转,输出旋转数组中的最小元素

    //旋转数组的最小数字 //题目:把一个数组最開始的若干个元素搬到数组的末尾.我们称之为数组的旋转. //输入一个递增排序的数组的一个旋转.输出旋转数组中的最小元素. //比如:数组{3.4,5,1, ...

  8. java中StringBuilder、StringBuffer、String类之间的关系

    今天在CSDN的高校俱乐部里看到了"Java基础水平測试(英文)".感觉自己学了java这么久,想看下自己的java水平究竟是个什么样.測试结果就不说了,反正是慘不忍睹. 看了一下 ...

  9. 在CentOS下安装tomcat并配置环境变量(改默认端口8080为8081)

    不多说,直接上干货! 第一步:下载tomcat压缩包 http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.73/bin/ 第二步:上传tomcat压 ...

  10. Swift学习笔记(8):闭包

    目录: 基本语法 尾随闭包 值捕获 自动闭包 闭包是自包含的函数代码块,闭包采取如下三种形式之一: ・全局函数是一个有名字但不会捕获任何值的闭包 ・嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包 ...