本篇文章的项目地址基于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. Golang 源码剖析:log 标准库

    Golang 源码剖析:log 标准库 原文地址:Golang 源码剖析:log 标准库 日志 输出 2018/09/28 20:03:08 EDDYCJY Blog... 构成 [日期]<空格 ...

  2. Image Processing for Very Large Images

    The key idea here is the partial image descriptor VIPS(VASARI Image Processing System) 是近几年逐渐兴起的针对大图 ...

  3. ASP.NET-前台view返回model集合

    有时操作列表的时候想一次提交一个model集合,这样后台controller直接接受后就可以直接进行操作了,不用使用js,比较方便,也体现了MVC的Binding模式的优势,方法如下: 准备: 1.两 ...

  4. WinServer-IIS-SEO优化

    来自为知笔记(Wiz)

  5. Unique path ii

    Follow up for "Unique Paths": Now consider if some obstacles are added to the grids. How m ...

  6. CentOS6.3升级GCC到GCC4.8.2

    server上安装的GCC版本号过旧.已经不满足个人使用的版本号需求,故决定对其进行升级操作.由当前版本号3.4.6升级到4.8.2.然受权限制约.仅仅能安装到个人文件夹.因此假设您的server能够 ...

  7. ORA-01950: 表空间'USERS'中无权限的2种解决办法

    在创建了一个新的表空间和一个新的用户,当用这个新用户创建表时, 却出现:ORA-01950: 表空 间'USERS'中无权限.   我已经把创建表的权限赋给了此用户,怎么还会缺少权限呢?解决办法   ...

  8. [LeetCode] Longest Substring Without Repeating Characters 最长无重复字符的子串 C++实现java实现

    最长无重复字符的子串 Given a string, find the length of the longest substring without repeating characters. Ex ...

  9. oracle中对字符串进行分割,并反回随机段

    --测试数据 select fun_spilt_draw('1,2,3,4,5,6,7') a from dual--待处理数据 strtext--定义一个函数分割 返回字符串中的一个随机段creat ...

  10. Android 数据存取

    Android提供了Preference(配置),File(文件),SQLite数据和网络几种数据存取方式 SharedPreference提供了一种轻量级的数据存取方法,应用场合主要是比较少的配置信 ...