iOS 文字處理相關(guān)
轉(zhuǎn)載http://ios.jobbole.com/90856/
在 iOS 開發(fā)中,文字處理可以說是最基礎(chǔ)常見的一部分內(nèi)容郭脂,系統(tǒng)控件例如 UILabel伏嗜、UITextField味悄、UITextView 等幫我們做了很多工作讓我們可以很方便地展示一段文本。但是當(dāng)我們要做更深入的定制展示時(shí)蜀涨,系統(tǒng)提供的這些控件就無法滿足需求了,這時(shí)候就要深入去了解一下系統(tǒng)是如何處理文本展示的蝎毡。
以下內(nèi)容是我在探究 iOS 系統(tǒng)對(duì)文字處理時(shí)做的一些記錄厚柳,主要是一些概念性的內(nèi)容,讓大家對(duì) iOS 文字處理有一個(gè)大概了解沐兵,涉及到具體需求時(shí)知道查找哪方便的資料别垮,包括了 Unicode、UIFont扎谎、TextKit碳想、CoreText、Unicode雙向算法等毁靶。
Nsstring 和 Unicode
(一)歷史
計(jì)算機(jī)沒法直接處理文本胧奔,它只和數(shù)字打交道。為了在計(jì)算機(jī)里用數(shù)字表示文本预吆,指定了一個(gè)從字符到數(shù)字的映射葡盗。這個(gè)映射就叫做編碼(encoding)。最開始的映射是 ASCII 編碼,但是能表示的字符有限觅够,因此后來用 Unicode 統(tǒng)一編碼胶背。
(二)Unicode概要
Unicode 可以看做是對(duì)各編碼系統(tǒng)的統(tǒng)一,但不通用喘先,有一些很古老的編碼系統(tǒng)無法兼容钳吟。
(三)Unicode特性
Unicode 以抽象的方式代表一個(gè)字符,而不規(guī)定這個(gè)字符如何渲染(render)窘拯。
組合字符序列红且,有些字符可以由單一碼點(diǎn)或由多個(gè)碼點(diǎn)組成,雖然外觀和意義相同涤姊,在 Unicode 語(yǔ)境下并不相等暇番,但符合 canonically equivalent
(四)Unicode格式轉(zhuǎn)換
UTF(Unicode Transformation Formats)
(五)NSString
關(guān)于 NSString,最需要記住的是:NSString 代表的是用 UTF-16 編碼的文本思喊,長(zhǎng)度壁酬、索引和范圍都基于 UTF-16 的碼元。對(duì)于這一點(diǎn)要是不注意會(huì)有以下一些陷阱:
長(zhǎng)度
我們經(jīng)常用 NSString 的 length 方法來獲取一個(gè)字符串的長(zhǎng)度恨课,在大多數(shù)情況下這個(gè)方法都沒有問題舆乔,但是當(dāng)一個(gè)字符串中包含 emoji 時(shí),這個(gè)返回的長(zhǎng)度值并不準(zhǔn)確剂公,以下是具體例子:
NSString *s = @"\U0001F30D"; // earth globe emoji ??
NSLog(@"The length of %@ is %lu", s, [s length]);
// => The length of ?? is 2
以下代碼可以獲取實(shí)際的長(zhǎng)度
NSUInteger realLength =
[s lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4;
NSLog(@"The real length of %@ is %lu", s, realLength);
// => The real length of ?? is 1
隨機(jī)訪問
用 characterAtIndex: 方法以索引方式直接訪問 unichar 會(huì)有同樣的問題希俩。可以用 rangeOfComposedCharacterSequenceAtIndex: 來確定特定位置的 unichar 是不是代表單個(gè)字符(可能由多個(gè)碼點(diǎn)組成)的碼元序列的一部分纲辽。每當(dāng)給另一個(gè)方法傳入一個(gè)內(nèi)容未知的字符串的范圍作參數(shù)時(shí)都應(yīng)該這樣做颜武,確保 Unicode 字符不會(huì)被從中間分開。
遍歷
使用 rangeOfComposedCharacterSequenceAtIndex: 的時(shí)候拖吼,可以寫一個(gè)代碼套路來正確地循環(huán)字符串里所有的字符盒刚,但每次要遍歷一個(gè)字符串時(shí)都得這樣做太不方便了。幸運(yùn)的是绿贞,NSString 有更好地方式:enumerateSubstringsInRange:options:usingBlock: 方法因块。這個(gè)方法把 Unicode 抽象的地方隱藏了,能讓你輕松地循環(huán)字符串里的組合字符串籍铁、單詞涡上、行、句子或段落拒名。你甚至可以加上 NSStringEnumerationLocalized 這個(gè)選項(xiàng)吩愧,這樣可以在確定詞語(yǔ)間和句子間的邊界時(shí)把用戶所在的區(qū)域考慮進(jìn)去。要遍歷單個(gè)字符增显,把參數(shù)指定為 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));
}];
比較
有些字符可以由單一碼點(diǎn)或由多個(gè)碼點(diǎn)組成雁佳,雖然外觀和意義相同,在 Unicode 語(yǔ)境下并不相等。isEqual: 和 isEqualToString: 這兩個(gè)方法都是一個(gè)字節(jié)一個(gè)字節(jié)地比較的糖权。如果希望字符串的合成和分解的形式相吻合堵腹,得先自己正規(guī)化:
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"; // ? ligature
NSComparisonResult result = [s localizedCompare:t];
NSLog(@"%@ is %@ to %@", s, result == NSOrderedSame ? @"equal" : @"not equal", t);
// => ff is equal to ?
上面這些內(nèi)容可以點(diǎn)擊這里了解更多。
TextKit
下圖列出了 iOS 系統(tǒng)下和文字處理相關(guān)的組件星澳,其中 TextKit 是從 iOS7 開始引入的疚顷,旨在幫助開發(fā)者實(shí)現(xiàn)更多的文本定制。
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.
以上引用了一段官網(wǎng)對(duì) Text Kit 的介紹禁偎,翻譯過來就是:Text Kit 是一系列類和協(xié)議腿堤,這些類和協(xié)議提供了高性能的排版服務(wù),這個(gè)服務(wù)可以讓應(yīng)用以很好的排版形式存儲(chǔ)如暖、布局和展示所有的字符笆檀,比如字間距、連筆盒至、斷行酗洒、兩端對(duì)齊。
Text Kit 中有幾個(gè)關(guān)鍵的組件妄迁,如下圖所示寝蹈,
layoutManager 將 textStorage 存儲(chǔ)的內(nèi)容根據(jù) textContainers 定義的區(qū)域布局到 textViews(UITextView)里李命。
在 MVC 中登淘,textStorage 和 textContainers 相當(dāng)于 M,textViews 相當(dāng)于 V封字,leyoutManager 相當(dāng)于 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 字符轉(zhuǎn)換成 glyphs(字形),并在 NSTextContainer 定義的范圍內(nèi)布局這些字形阔籽。
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 會(huì)做如下一系列操作:
控制 text storage 和 text container
Unicode 字符轉(zhuǎn)換成 glyphs(字形)
計(jì)算字形的位置信息并保存起來
管理字符的范圍信息
將字形繪制到視圖上
計(jì)算每一行的矩形包裹信息
處理斷字
處理文字的屬性流妻,例如字體、顏色笆制、下標(biāo)
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:字體绅这、顏色、下標(biāo)
Paragraph attributes:縮進(jìn)在辆、制表符证薇、行距
Document attributes:頁(yè)數(shù)、頁(yè)間距匆篓、頁(yè)縮放比例
UIFont
我們可以通過設(shè)置不同的字體來改變字符渲染到頁(yè)面上的樣式,UIFont 里有一些度量信息(metrics)鸦概,如下圖所示箩张,
這些信息在 UIFont 里都能獲取到,以下是它們的對(duì)應(yīng)關(guān)系
UIFont 的 metrics 有一個(gè)具體應(yīng)用,比如我們想讓一個(gè)區(qū)域最多顯示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;
}
CTFramesetter生成CTFrame,每一個(gè)CTFrame代表一個(gè)段落赴肚。一個(gè)CTFrame可以只是一行很長(zhǎng)的CTLine或者包含多個(gè)CTLine素跺,一個(gè)CTLine代表一行文本。
Unicode雙向算法
雙向文字是指同時(shí)包含了兩種書寫方向的文字誉券,也就是從左到右和從右到左的文字同時(shí)存在指厌,默認(rèn)根據(jù)第一個(gè)字符的 Unicode 屬性定義全局方向。
書寫方向與文字相關(guān)踊跟,與語(yǔ)言的關(guān)系不大踩验。一種語(yǔ)言可能有多種文字,使用英語(yǔ)時(shí)從左到右書寫商玫,使用阿拉伯語(yǔ)時(shí)使用從右到左書寫箕憾。
這些被加入文字中的 Unicode 控制字符在顯示界面上是不可見的,也不占用任何顯示空間拳昌。它們只是在默默地影響著雙向文字的顯示袭异。
Unicode 控制字符又可以分為兩類,
第一類為隱性雙向控制字符:
U+200E: LEFT-TO-RIGHT MARK (LRM)
U+200F: RIGHT-TO-LEFT MARK (RLM
簡(jiǎn)單來說炬藤,您可以將這類的控制字符看成是不會(huì)顯示出來的強(qiáng)字符御铃,LRM 為從左到右的強(qiáng)字符,而 RLM 為從右到左的強(qiáng)字符沈矿。
而第二類當(dāng)然就是顯性雙向控制字符:
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)
這類控制字符需要成對(duì)使用上真,列表中的前四個(gè)為開始字符,而最后一個(gè)為結(jié)束字符羹膳。當(dāng)雙向算法遇到 LRE 時(shí)睡互,接下來文字片段內(nèi)的方向開始變?yōu)閺淖蟮接摇.?dāng)雙向算法遇到 RLE 時(shí)陵像,接下來文字片段內(nèi)的方向開始變?yōu)閺挠业阶缶椭椤.?dāng)遇到 LRO 時(shí),雙向算法會(huì)將后面所有文字的雙向?qū)傩砸暈閺淖蟮接覐?qiáng)字符蠢壹。當(dāng)遇到 RLO 時(shí)嗓违,雙向算法會(huì)將后面所有文字的雙向?qū)傩砸暈閺挠业阶髲?qiáng)字符。如果一旦遇到 PDF 字符图贸,雙向?qū)傩缘臓顟B(tài)就會(huì)恢復(fù)到最后一個(gè) LRE蹂季、RLE冕广、LRO 或 RLO 之前的狀態(tài)。
更多資料參考
http://www.ibm.com/developerworks/cn/web/1404_xiayin_bidihtml/
http://www.iamcal.com/understanding-bidirectional-text/