文字处理

iOS 文字处理相关
转载http://ios.jobbole.com/90856/
在 iOS 开发中,文字处理可以说是最基础常见的一部分内容,系统控件例如 UILabel、UITextField、UITextView 等帮我们做了很多工作让我们可以很方便地展示一段文本。但是当我们要做更深入的定制展示时,系统提供的这些控件就无法满足需求了,这时候就要深入去了解一下系统是如何处理文本展示的。
以下内容是我在探究 iOS 系统对文字处理时做的一些记录,主要是一些概念性的内容,让大家对 iOS 文字处理有一个大概了解,涉及到具体需求时知道查找哪方便的资料,包括了 Unicode、UIFont、TextKit、CoreText、Unicode双向算法等。
Nsstring 和 Unicode
(一)历史
计算机没法直接处理文本,它只和数字打交道。为了在计算机里用数字表示文本,指定了一个从字符到数字的映射。这个映射就叫做编码(encoding)。最开始的映射是 ASCII 编码,但是能表示的字符有限,因此后来用 Unicode 统一编码。
(二)Unicode概要
Unicode 可以看做是对各编码系统的统一,但不通用,有一些很古老的编码系统无法兼容。
(三)Unicode特性
Unicode 以抽象的方式代表一个字符,而不规定这个字符如何渲染(render)。
组合字符序列,有些字符可以由单一码点或由多个码点组成,虽然外观和意义相同,在 Unicode 语境下并不相等,但符合 canonically equivalent

(四)Unicode格式转换
UTF(Unicode Transformation Formats)
(五)NSString
关于 NSString,最需要记住的是:NSString 代表的是用 UTF-16 编码的文本,长度、索引和范围都基于 UTF-16 的码元。对于这一点要是不注意会有以下一些陷阱:
长度

我们经常用 NSString 的 length 方法来获取一个字符串的长度,在大多数情况下这个方法都没有问题,但是当一个字符串中包含 emoji 时,这个返回的长度值并不准确,以下是具体例子:

NSString *s = @"\U0001F30D"; // earth globe emoji 🌍
NSLog(@"The length of %@ is %lu", s, [s length]);
// => The length of 🌍 is 2

以下代码可以获取实际的长度

NSUInteger realLength =
 [s lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4;
 NSLog(@"The real length of %@ is %lu", s, realLength);
 
 // => The real length of 🌍 is 1

随机访问

用 characterAtIndex: 方法以索引方式直接访问 unichar 会有同样的问题。可以用 rangeOfComposedCharacterSequenceAtIndex: 来确定特定位置的 unichar 是不是代表单个字符(可能由多个码点组成)的码元序列的一部分。每当给另一个方法传入一个内容未知的字符串的范围作参数时都应该这样做,确保 Unicode 字符不会被从中间分开。
遍历

使用 rangeOfComposedCharacterSequenceAtIndex: 的时候,可以写一个代码套路来正确地循环字符串里所有的字符,但每次要遍历一个字符串时都得这样做太不方便了。幸运的是,NSString 有更好地方式:enumerateSubstringsInRange:options:usingBlock: 方法。这个方法把 Unicode 抽象的地方隐藏了,能让你轻松地循环字符串里的组合字符串、单词、行、句子或段落。你甚至可以加上 NSStringEnumerationLocalized 这个选项,这样可以在确定词语间和句子间的边界时把用户所在的区域考虑进去。要遍历单个字符,把参数指定为 NSStringEnumerationByComposedCharacterSequences:

NSString *s = @"The weather on \U0001F30D is \U0001F31E today.";
// The weather on 🌍 is 🌞 today.
NSRange fullRange = NSMakeRange(0, [s length]);
[s enumerateSubstringsInRange:fullRange
                      options:NSStringEnumerationByComposedCharacterSequences
                   usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop)
{
    NSLog(@"%@ %@", substring, NSStringFromRange(substringRange));
}];

比较

有些字符可以由单一码点或由多个码点组成,虽然外观和意义相同,在 Unicode 语境下并不相等。isEqual: 和 isEqualToString: 这两个方法都是一个字节一个字节地比较的。如果希望字符串的合成和分解的形式相吻合,得先自己正规化:

NSString *s = @"\u00E9"; // é
NSString *t = @"e\u0301"; // e + ´
BOOL isEqual = [s isEqualToString:t];
NSLog(@"%@ is %@ to %@", s, isEqual ? @"equal" : @"not equal", t);
// => é is not equal to é
 
// Normalizing to form C
NSString *sNorm = [s precomposedStringWithCanonicalMapping];
NSString *tNorm = [t precomposedStringWithCanonicalMapping];
BOOL isEqualNorm = [sNorm isEqualToString:tNorm];
NSLog(@"%@ is %@ to %@", sNorm, isEqualNorm ? @"equal" : @"not equal", tNorm);
 
// => é is equal to é
 
NSString *s = @"ff"; // ff
NSString *t = @"\uFB00"; // ff ligature
NSComparisonResult result = [s localizedCompare:t];
NSLog(@"%@ is %@ to %@", s, result == NSOrderedSame ? @"equal" : @"not equal", t);
// => ff is equal to ff

上面这些内容可以点击这里了解更多。
TextKit
下图列出了 iOS 系统下和文字处理相关的组件,其中 TextKit 是从 iOS7 开始引入的,旨在帮助开发者实现更多的文本定制。

Text Kit is a set of classes and protocols that provide high-quality typographical services which enable apps to store, lay out, and display text with all the characteristics of fine typesetting, such as kerning, ligatures, line breaking, and justification.

以上引用了一段官网对 Text Kit 的介绍,翻译过来就是:Text Kit 是一系列类和协议,这些类和协议提供了高性能的排版服务,这个服务可以让应用以很好的排版形式存储、布局和展示所有的字符,比如字间距、连笔、断行、两端对齐。
Text Kit 中有几个关键的组件,如下图所示,

layoutManager 将 textStorage 存储的内容根据 textContainers 定义的区域布局到 textViews(UITextView)里。
在 MVC 中,textStorage 和 textContainers 相当于 M,textViews 相当于 V,leyoutManager 相当于 C。
An NSLayoutManager object orchestrates the operation of the other text handling objects. It intercedes in operations that convert the data in an NSTextStorage object to rendered text in a view’s display area. It maps Unicode character codes to glyphs and oversees the layout of the glyphs within the areas defined by NSTextContainer objects.

NSLayoutManager 将 Unicode 字符转换成 glyphs(字形),并在 NSTextContainer 定义的范围内布局这些字形。
The layout manager performs the following actions:
Controls text storage and text container objects
Generates glyphs from characters
Computes glyph locations and stores the information
Manages ranges of glyphs and characters
Draws glyphs in text views when requested by the view
Computes bounding box rectangles for lines of text
Controls hyphenation
Manipulates character attributes and glyph properties

layout manager 会做如下一系列操作:
控制 text storage 和 text container
Unicode 字符转换成 glyphs(字形)
计算字形的位置信息并保存起来
管理字符的范围信息
将字形绘制到视图上
计算每一行的矩形包裹信息
处理断字
处理文字的属性,例如字体、颜色、下标

Text Kit handles three kinds of text attributes:
character attributes, paragraph attributes, and document attributes.
Character attributes include traits such as font, color, and subscript, which can be associated with an individual character or a range of characters.
Paragraph attributes are traits such as indentation, tabs, and line spacing. Document attributes include documentwide traits such as paper size, margins, and view zoom percentage.

Character attributes:字体、颜色、下标
Paragraph attributes:缩进、制表符、行距
Document attributes:页数、页间距、页缩放比例

下面列出了一些 Text Kit 的常见用法,


15128529-7d16b3dbc4b59a7e

UIFont
我们可以通过设置不同的字体来改变字符渲染到页面上的样式,UIFont 里有一些度量信息(metrics),如下图所示,

这些信息在 UIFont 里都能获取到,以下是它们的对应关系

UIFont 的 metrics 有一个具体应用,比如我们想让一个区域最多显示6行的文本,如果使用 UILabel,我们可以指定 numberOfLines 属性,在不使用 UILabel 的情况下,我们就可以用到 UIFont 的 lineHeight 属性了,用法如下:

+ (float)calculateContentHeight:(NSString *)content{
 
    UIFont *font = [UIFont systemFontOfSize:13];
    CGFloat lineHeight = font.lineHeight;
 
    int height = 0;
    float max_width = SCREEN_WIDTH-30;
    float max_height = ceil(lineHeight)*6;
 
    CGSize content_size = [content sizeWithFont:font constrainedToSize:CGSizeMake(max_width, MAXFLOAT) lineBreakMode:NSLineBreakByWordWrapping];
    height = ceil(content_size.height);
    if (content.length == 0) {
        return 0;
    }
 
    height = MIN(max_height, height);
    return height;
}

CoreText

CTFramesetter生成CTFrame,每一个CTFrame代表一个段落。一个CTFrame可以只是一行很长的CTLine或者包含多个CTLine,一个CTLine代表一行文本。
Unicode双向算法
双向文字是指同时包含了两种书写方向的文字,也就是从左到右和从右到左的文字同时存在,默认根据第一个字符的 Unicode 属性定义全局方向。
书写方向与文字相关,与语言的关系不大。一种语言可能有多种文字,使用英语时从左到右书写,使用阿拉伯语时使用从右到左书写。

这些被加入文字中的 Unicode 控制字符在显示界面上是不可见的,也不占用任何显示空间。它们只是在默默地影响着双向文字的显示。
Unicode 控制字符又可以分为两类,
第一类为隐性双向控制字符:
U+200E: LEFT-TO-RIGHT MARK (LRM)
U+200F: RIGHT-TO-LEFT MARK (RLM

简单来说,您可以将这类的控制字符看成是不会显示出来的强字符,LRM 为从左到右的强字符,而 RLM 为从右到左的强字符。
而第二类当然就是显性双向控制字符:
U+202A: LEFT-TO-RIGHT EMBEDDING (LRE)
U+202B: RIGHT-TO-LEFT EMBEDDING (RLE)
U+202D: LEFT-TO-RIGHT OVERRIDE (LRO)
U+202E: RIGHT-TO-LEFT OVERRIDE (RLO)
U+202C: POP DIRECTIONAL FORMATTING (PDF)

这类控制字符需要成对使用,列表中的前四个为开始字符,而最后一个为结束字符。当双向算法遇到 LRE 时,接下来文字片段内的方向开始变为从左到右。当双向算法遇到 RLE 时,接下来文字片段内的方向开始变为从右到左。当遇到 LRO 时,双向算法会将后面所有文字的双向属性视为从左到右强字符。当遇到 RLO 时,双向算法会将后面所有文字的双向属性视为从右到左强字符。如果一旦遇到 PDF 字符,双向属性的状态就会恢复到最后一个 LRE、RLE、LRO 或 RLO 之前的状态。
更多资料参考
http://www.ibm.com/developerworks/cn/web/1404_xiayin_bidihtml/
http://www.iamcal.com/understanding-bidirectional-text/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容