Core Text 入門(富文本)

本文所涉及的代碼你可以在這里下載到https://github.com/kejinlu/CTTest,包含兩個項目幻捏,一個Mac的NSTextView的測試項目前普,一個iOS的Core Text的測試項目

NSTextView和Attribued String

第一次接觸蘋果系的富文本編程是在寫Mac平臺上的一個輸入框的時候宏所,輸入框中的文字可以設(shè)置各種樣式酥艳,并可以在文字中間插入圖片,好在Mac的AppKit中提供了NSTextView這個支持富文本編輯器控件爬骤。此控件背后是通過什么方式來描述富文本的呢充石?答案是NSAttributedString,很多編程語言都提供了AttributedString的概念霞玄。NSAttributedString比NSString多了一個Attribute的概念赫冬,一個NSAttributedString的對象包含很多的屬性浓镜,每一個屬性都有其對應(yīng)的字符區(qū)域,在這里是使用NSRange來進行描述的劲厌。下面是一個NSTextView顯示富文本的例子

NSMutableAttributedString*attributedString=[[[NSMutableAttributedString alloc] initWithString:@"測試富文本顯示"] autorelease];

//為所有文本設(shè)置字體

[attributedString addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:24] range:NSMakeRange(0, [attributedString length])];

//將“測試”兩字字體顏色設(shè)置為藍色

[attributedString addAttribute:NSForegroundColorAttributeName value:[NSColor blueColor] range:NSMakeRange(0,2)];

//將“富文本”三個字字體顏色設(shè)置為紅色

[attributedString addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:NSMakeRange(2,3)];

//在“測”和“試”兩字之間插入一張圖片

NSString*imageName=@"taobao.png";

NSFileWrapper*imageFileWrapper=[[[NSFileWrapper alloc] initRegularFileWithContents:[[NSImage imageNamed:imageName] TIFFRepresentation]] autorelease];

imageFileWrapper.filename=imageName;

imageFileWrapper.preferredFilename=imageName;

NSTextAttachment*imageAttachment=[[[NSTextAttachment alloc] initWithFileWrapper:imageFileWrapper] autorelease];

NSAttributedString*imageAttributedString=[NSAttributedString attributedStringWithAttachment:imageAttachment];

[attributedString insertAttributedString:imageAttributedString atIndex:1];

/*

其實插入圖片附件之后 attributedString的長度增加了1 變成了8,所以可以預(yù)見其實圖片附件屬性對應(yīng)的內(nèi)容應(yīng)該是一個長度的字符

Printing description of attributedString:

測{

NSColor = "NSCalibratedRGBColorSpace 0 0 1 1";

NSFont = "\"LucidaGrande 24.00 pt. P [] (0x10051bfd0) fobj=0x101e687f0, spc=7.59\"";

}{

NSAttachment = " \"taobao.png\"";

}試{

NSColor = "NSCalibratedRGBColorSpace 0 0 1 1";

NSFont = "\"LucidaGrande 24.00 pt. P [] (0x10051bfd0) fobj=0x101e687f0, spc=7.59\"";

}富文本{

NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";

NSFont = "\"LucidaGrande 24.00 pt. P [] (0x10051bfd0) fobj=0x101e687f0, spc=7.59\"";

}顯示{

NSFont = "\"LucidaGrande 24.00 pt. P [] (0x10051bfd0) fobj=0x101e687f0, spc=7.59\"";

}

*/

[_textView insertText:attributedString];

還有就是NSAttributedString提供對所有屬性進行遍歷的方法听隐,也提供了計算在特定size下渲染實際所占的區(qū)域(boundingRectWithSize:options:) 這些這里就不介紹了补鼻。 從上面的代碼可以看出其實Mac下的富文本的渲染并不是很復(fù)雜,只要將Attributed String理解和使用好雅任,其余的事情都交給NSTextView來做了风范,你完全不用考慮其底層是如何取渲染的。但是在iOS平臺上就沒有這么幸運了沪么,雖然iOS從3硼婿。2開始也提供了NSAttributedString,但是并沒有類似NSTextView這樣的控件直接來渲染Attributed String禽车。 這個時候你就得使用Core Text了寇漫。

Core Text

下面討論的Core Text相關(guān)編程都是特指在iOS平臺下。 Core Text是和Core Graphics配合使用的殉摔,一般是在UIView的drawRect方法中的Graphics Context上進行繪制的州胳。 且Core Text真正負責(zé)繪制的是文本部分,圖片還是需要自己去手動繪制逸月,所以你必須關(guān)注很多繪制的細節(jié)部分栓撞。

一.Core Text知識準備

在進入任何一個新的編程領(lǐng)域之前,我們肯定要先接觸相關(guān)的領(lǐng)域模型的知識碗硬。比如你軟件是進行科學(xué)計算的瓤湘,那么你就必須理解大量的數(shù)學(xué)原理;如果你的軟件是搞銀行系統(tǒng)恩尾,那么你就得事先了解相關(guān)的銀行的業(yè)務(wù)知識弛说。這些都是不可避免的事情。通常情況下領(lǐng)域知識具有較高的通用性特笋。但在特定的環(huán)境下剃浇,某些知識點也會被特殊處理。 Core Text是用來進行文字精細排版的猎物,所以了解文字相關(guān)的知識也不可避免虎囚。

1.字符(Character)和字形(Glyphs)

排版系統(tǒng)中文本顯示的一個重要的過程就是字符到字形的轉(zhuǎn)換,字符是信息本身的元素蔫磨,而字形是字符的圖形表征淘讥,字符還會有其它表征比如發(fā)音。 字符在計算機中其實就是一個編碼堤如,某個字符集中的編碼蒲列,比如Unicode字符集窒朋,就囊括了大都數(shù)存在的字符。 而字形則是圖形蝗岖,一般都存儲在字體文件中侥猩,字形也有它的編碼,也就是它在字體中的索引抵赢。 一個字符可以對應(yīng)多個字形(不同的字體欺劳,或者同種字體的不同樣式:粗體斜體等);多個字符也可能對應(yīng)一個字形铅鲤,比如字符的連寫( Ligatures)划提。

Roman Ligatures

下面就來詳情看看字形的各個參數(shù)也就是所謂的字形度量Glyph Metrics

bounding box(邊界框 bbox),這是一個假想的框子邢享,它盡可能緊密的裝入字形鹏往。

baseline(基線),一條假想的線,一行上的字形都以此線作為上下位置的參考骇塘,在這條線的左側(cè)存在一個點叫做基線的原點伊履,

ascent(上行高度)從原點到字體中最高(這里的高深都是以基線為參照線的)的字形的頂部的距離,ascent是一個正值

descent(下行高度)從原點到字體中最深的字形底部的距離绪爸,descent是一個負值(比如一個字體原點到最深的字形的底部的距離為2湾碎,那么descent就為-2)

linegap(行距),linegap也可以稱作leading(其實準確點講應(yīng)該叫做External leading),行高lineHeight則可以通過 ascent + |descent| + linegap 來計算奠货。

一些Metrics專業(yè)知識還可以參考Free Type的文檔Glyph metrics介褥,其實iOS就是使用Free Type庫來進行字體渲染的。

以上圖片和部分概念來自蘋果文檔Querying Font Metrics递惋,Text Layout

2.坐標系

首先不得不說 蘋果編程中的坐標系花樣百出柔滔,經(jīng)常讓開發(fā)者措手不及。 傳統(tǒng)的Mac中的坐標系的原點在左下角萍虽,比如NSView默認的坐標系睛廊,原點就在左下角。但Mac中有些View為了其實現(xiàn)的便捷將原點變換到左上角杉编,像NSTableView的坐標系坐標原點就在左上角超全。iOS UIKit的UIView的坐標系原點在左上角。

往底層看邓馒,Core Graphics的context使用的坐標系的原點是在左下角嘶朱。而在iOS中的底層界面繪制就是通過Core Graphics進行的,那么坐標系列是如何變換的呢光酣? 在UIView的drawRect方法中我們可以通過UIGraphicsGetCurrentContext()來獲得當(dāng)前的Graphics Context疏遏。drawRect方法在被調(diào)用前,這個Graphics Context被創(chuàng)建和配置好,你只管使用便是财异。如果你細心倘零,通過CGContextGetCTM(CGContextRef c)可以看到其返回的值并不是CGAffineTransformIdentity,通過打印出來看到值為

Printing description of contextCTM:

(CGAffineTransform) contextCTM = {

a = 1

b = 0

c = 0

d = -1

tx = 0

ty = 460

}

這是非retina分辨率下的結(jié)果戳寸,如果是如果是retina上面的a,d,ty的值將會乘2呈驶,如果是iPhone 5,ty的值會再大些庆揩。 但是作用都是一樣的就是將上下文空間坐標系進行了flip俐东,使得原本左下角原點變到左上角,y軸正方向也變換成向下订晌。

上面說了一大堆,下面進入正題蚌吸,Core Text一開始便是定位于桌面的排版系統(tǒng)锈拨,使用了傳統(tǒng)的原點在左下角的坐標系,所以它在繪制文本的時候都是參照左下角的原點進行繪制的羹唠。 但是iOS的UIView的drawRect方法的context被做了次flip奕枢,如果你啥也不做處理,直接在這個context上進行Core Text繪制佩微,你會發(fā)現(xiàn)文字是鏡像且上下顛倒缝彬。

所以在UIView的drawRect方法中的context上進行Core Text繪制之前需要對context進行一次Flip。

這里再提及一個函數(shù)CGContextSetTextMatrix哺眯,它可以用來為每一個顯示的字形單獨設(shè)置變形矩陣谷浅。

3.NSMutableAttributedString 和 CFMutableAttributedStringRef

Core Foundation和Foundation中的有些數(shù)據(jù)類型只需要簡單的強制類型轉(zhuǎn)換就可以互換使用,這類類型我們叫他們?yōu)?a target="_blank" rel="nofollow">Toll-Free Bridged Types奶卓。

CFMutableAttributedStringRef和NSMutableAttributedString就是其中的一對一疯,Core Foundation的接口基本是C的接口,功能強大夺姑,但是使用起來沒有Foundation中提供的Objc的接口簡單好使墩邀,所以很多時候我們可以使用高層接口組織數(shù)據(jù),然后將其傳給低層函數(shù)接口使用盏浙。

二.Core Text對象模型

這節(jié)主要來看看Core Text繪制的一些細節(jié)問題了眉睹,首先是Core Text繪制的流程:

framesetter framesetter對應(yīng)的類型是 CTFramesetter,通過CFAttributedString進行初始化废膘,它作為CTFrame對象的生產(chǎn)工廠竹海,負責(zé)根據(jù)path生產(chǎn)對應(yīng)的CTFrame

CTFrame CTFrame是可以通過CTFrameDraw函數(shù)直接繪制到context上的,當(dāng)然你可以在繪制之前殖卑,操作CTFrame中的CTLine站削,進行一些參數(shù)的微調(diào)

CTLine 可以看做Core Text繪制中的一行的對象 通過它可以獲得當(dāng)前行的line ascent,line descent ,line leading,還可以獲得Line下的所有Glyph Runs

CTRun 或者叫做 Glyph Run,是一組共享想相同attributes(屬性)的字形的集合體

上面說了這么多對也沒一個東西和圖片繪制有關(guān)系孵稽,其實吧许起,Core Text本身并不支持圖片繪制十偶,圖片的繪制你還得通過Core Graphics來進行。只是Core Text可以通過CTRun的設(shè)置為你的圖片在文本繪制的過程中留出適當(dāng)?shù)目臻g园细。這個設(shè)置就使用到CTRunDelegate了惦积,看這個名字大概就可以知道什么意思了,CTRunDelegate作為CTRun相關(guān)屬性或操作擴展的一個入口猛频,使得我們可以對CTRun做一些自定義的行為狮崩。為圖片留位置的方法就是加入一個空白的CTRun,自定義其ascent鹿寻,descent睦柴,width等參數(shù),使得繪制文本的時候留下空白位置給相應(yīng)的圖片毡熏。然后圖片在相應(yīng)的空白位置上使用Core Graphics接口進行繪制坦敌。

使用CTRunDelegateCreate可以創(chuàng)建一個CTRunDelegate,它接收兩個參數(shù)痢法,一個是callbacks結(jié)構(gòu)體狱窘,一個是所有callback調(diào)用的時候需要傳入的對象。 callbacks的結(jié)構(gòu)體為CTRunDelegateCallbacks财搁,主要是包含一些回調(diào)函數(shù)蘸炸,比如有返回當(dāng)前run的ascent,descent尖奔,width這些值的回調(diào)函數(shù)搭儒,至于函數(shù)中如何鑒別當(dāng)前是哪個run,可以在CTRunDelegateCreate的第二個參數(shù)來達到目的越锈,因為CTRunDelegateCreate的第二個參數(shù)會作為每一個回調(diào)調(diào)用時的入?yún)ⅰ?/p>

三.Core Text實戰(zhàn)

這里使用Core Text實現(xiàn)一個和之前NSTextView顯示類似的圖文混排的例子仗嗦。

直接貼上代碼大家體會下:

voidRunDelegateDeallocCallback(void*refCon ){

}

CGFloatRunDelegateGetAscentCallback(void*refCon ){

NSString*imageName=(NSString*)refCon;

return[UIImage imageNamed:imageName].size.height;

}

CGFloatRunDelegateGetDescentCallback(void*refCon){

return0;

}

CGFloatRunDelegateGetWidthCallback(void*refCon){

NSString*imageName=(NSString*)refCon;

return[UIImage imageNamed:imageName].size.width;

}

-(void)drawRect:(CGRect)rect

{

CGContextRef context=UIGraphicsGetCurrentContext();

//這四行代碼只是簡單測試drawRect中context的坐標系

CGContextSetRGBFillColor (context,1,0,0,1);

CGContextFillRect (context, CGRectMake (0,200,200,100));

CGContextSetRGBFillColor (context,0,0,1,.5);

CGContextFillRect (context, CGRectMake (0,200,100,200));

CGContextSetTextMatrix(context, CGAffineTransformIdentity);//設(shè)置字形變換矩陣為CGAffineTransformIdentity,也就是說每一個字形都不做圖形變換

CGAffineTransform flipVertical=CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height);

CGContextConcatCTM(context, flipVertical);//將當(dāng)前context的坐標系進行flip

NSMutableAttributedString*attributedString=[[[NSMutableAttributedString alloc] initWithString:@"測試富文本顯示"] autorelease];

//為所有文本設(shè)置字體

//[attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:24] range:NSMakeRange(0, [attributedString length])]; // 6.0+

UIFont*font=[UIFont systemFontOfSize:24];

CTFontRef fontRef=CTFontCreateWithName((CFStringRef)font.fontName, font.pointSize,NULL);

[attributedString addAttribute:(NSString*)kCTFontAttributeName value:(id)fontRef range:NSMakeRange(0, [attributedString length])];

//將“測試”兩字字體顏色設(shè)置為藍色

//[attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, 2)]; //6.0+

[attributedString addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[UIColor blueColor].CGColor range:NSMakeRange(0,2)];

//將“富文本”三個字字體顏色設(shè)置為紅色

//[attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(2, 3)]; //6.0+

[attributedString addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[UIColor redColor].CGColor range:NSMakeRange(2,3)];

//為圖片設(shè)置CTRunDelegate,delegate決定留給圖片的空間大小

NSString*taobaoImageName=@"taobao.png";

CTRunDelegateCallbacks imageCallbacks;

imageCallbacks.version=kCTRunDelegateVersion1;

imageCallbacks.dealloc=RunDelegateDeallocCallback;

imageCallbacks.getAscent=RunDelegateGetAscentCallback;

imageCallbacks.getDescent=RunDelegateGetDescentCallback;

imageCallbacks.getWidth=RunDelegateGetWidthCallback;

CTRunDelegateRef runDelegate=CTRunDelegateCreate(&imageCallbacks, taobaoImageName);

NSMutableAttributedString*imageAttributedString=[[NSMutableAttributedString alloc] initWithString:@" "];//空格用于給圖片留位置

[imageAttributedString addAttribute:(NSString*)kCTRunDelegateAttributeName value:(id)runDelegate range:NSMakeRange(0,1)];

CFRelease(runDelegate);

[imageAttributedString addAttribute:@"imageName"value:taobaoImageName range:NSMakeRange(0,1)];

[attributedString insertAttributedString:imageAttributedString atIndex:1];

CTFramesetterRef ctFramesetter=CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)attributedString);

CGMutablePathRef path=CGPathCreateMutable();

CGRect bounds=CGRectMake(0.0,0.0, self.bounds.size.width, self.bounds.size.height);

CGPathAddRect(path,NULL, bounds);

CTFrameRef ctFrame=CTFramesetterCreateFrame(ctFramesetter,CFRangeMake(0,0), path,NULL);

CTFrameDraw(ctFrame, context);

CFArrayRef lines=CTFrameGetLines(ctFrame);

CGPoint lineOrigins[CFArrayGetCount(lines)];

CTFrameGetLineOrigins(ctFrame, CFRangeMake(0,0), lineOrigins);

for(inti=0; i

CTLineRef line=CFArrayGetValueAtIndex(lines, i);

CGFloat lineAscent;

CGFloat lineDescent;

CGFloat lineLeading;

CTLineGetTypographicBounds(line,&lineAscent,&lineDescent,&lineLeading);

CFArrayRef runs=CTLineGetGlyphRuns(line);

for(intj=0; j

CGFloat runAscent;

CGFloat runDescent;

CGPoint lineOrigin=lineOrigins[i];

CTRunRef run=CFArrayGetValueAtIndex(runs, j);

NSDictionary*attributes=(NSDictionary*)CTRunGetAttributes(run);

CGRect runRect;

runRect.size.width=CTRunGetTypographicBounds(run, CFRangeMake(0,0),&runAscent,&runDescent,NULL);

runRect=CGRectMake(lineOrigin.x+CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location,NULL), lineOrigin.y-runDescent, runRect.size.width, runAscent+runDescent);

NSString*imageName=[attributes objectForKey:@"imageName"];

//圖片渲染邏輯

if(imageName) {

UIImage*image=[UIImage imageNamed:imageName];

if(image) {

CGRect imageDrawRect;

imageDrawRect.size=image.size;

imageDrawRect.origin.x=runRect.origin.x+lineOrigin.x;

imageDrawRect.origin.y=lineOrigin.y;

CGContextDrawImage(context, imageDrawRect, image.CGImage);

}

}

}

}

CFRelease(ctFrame);

CFRelease(path);

CFRelease(ctFramesetter);

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甘凭,一起剝皮案震驚了整個濱河市稀拐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丹弱,老刑警劉巖嘿棘,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歇万,死亡現(xiàn)場離奇詭異汹胃,居然都是意外死亡胯努,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門坯苹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隆檀,“玉大人,你說我怎么就攤上這事】致兀” “怎么了泉坐?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長裳仆。 經(jīng)常有香客問我腕让,道長,這世上最難降的妖魔是什么歧斟? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任纯丸,我火速辦了婚禮,結(jié)果婚禮上静袖,老公的妹妹穿的比我還像新娘觉鼻。我一直安慰自己,他們只是感情好队橙,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布滑凉。 她就那樣靜靜地躺著,像睡著了一般喘帚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咒钟,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天吹由,我揣著相機與錄音,去河邊找鬼朱嘴。 笑死倾鲫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萍嬉。 我是一名探鬼主播乌昔,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壤追!你這毒婦竟也來了磕道?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤行冰,失蹤者是張志新(化名)和其女友劉穎溺蕉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悼做,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡疯特,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肛走。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漓雅。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出邻吞,到底是詐尸還是另有隱情组题,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布吃衅,位于F島的核電站往踢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏徘层。R本人自食惡果不足惜峻呕,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望趣效。 院中可真熱鬧瘦癌,春花似錦、人聲如沸跷敬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽西傀。三九已至斤寇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拥褂,已是汗流浹背娘锁。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饺鹃,地道東北人莫秆。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像悔详,于是被迫代替她去往敵國和親镊屎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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