文字處理

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è)縮放比例

下面列出了一些 Text Kit 的常見用法浑度,


15128529-7d16b3dbc4b59a7e

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;
}

CoreText

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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末偿洁,一起剝皮案震驚了整個(gè)濱河市撒汉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涕滋,老刑警劉巖睬辐,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宾肺,居然都是意外死亡齐唆,警方通過查閱死者的電腦和手機(jī)状共,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門殿较,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泽裳,“玉大人,你說我怎么就攤上這事增拥∽那桑” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵掌栅,是天一觀的道長(zhǎng)秩仆。 經(jīng)常有香客問我,道長(zhǎng)猾封,這世上最難降的妖魔是什么澄耍? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮忘衍,結(jié)果婚禮上逾苫,老公的妹妹穿的比我還像新娘卿城。我一直安慰自己枚钓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布瑟押。 她就那樣靜靜地躺著搀捷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪多望。 梳的紋絲不亂的頭發(fā)上嫩舟,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音怀偷,去河邊找鬼家厌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛椎工,可吹牛的內(nèi)容都是我干的饭于。 我是一名探鬼主播蜀踏,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼掰吕!你這毒婦竟也來了果覆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤殖熟,失蹤者是張志新(化名)和其女友劉穎局待,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菱属,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钳榨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纽门。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片重绷。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖膜毁,靈堂內(nèi)的尸體忽然破棺而出昭卓,到底是詐尸還是另有隱情,我是刑警寧澤瘟滨,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布候醒,位于F島的核電站,受9級(jí)特大地震影響杂瘸,放射性物質(zhì)發(fā)生泄漏倒淫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一败玉、第九天 我趴在偏房一處隱蔽的房頂上張望敌土。 院中可真熱鬧,春花似錦运翼、人聲如沸返干。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)矩欠。三九已至,卻和暖如春悠夯,著一層夾襖步出監(jiān)牢的瞬間癌淮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工沦补, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乳蓄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓夕膀,卻偏偏與公主長(zhǎng)得像虚倒,于是被迫代替她去往敵國(guó)和親匣摘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容