第一次接觸蘋果系的富文本編程是在寫Mac平臺上的一個輸入框的時候,輸入框中的文字能夠設置各類樣式,並能夠在文字中間插入圖片,好在Mac的AppKit中提供了NSTextView這個支持富文本編輯器控件。此控件背後是經過什麼方式來描述富文本的呢?答案是NSAttributedString,不少編程語言都提供了AttributedString的概念。NSAttributedString比NSString多了一個Attribute的概念,一個NSAttributedString的對象包含不少的屬性,每個屬性都有其對應的字符區域,在這裏是使用NSRange來進行描述的。下面是一個NSTextView顯示富文本的例子 html
NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc] initWithString:@"測試富文本顯示"] autorelease]; //爲全部文本設置字體 [attributedString addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:24] range:NSMakeRange(0, [attributedString length])]; //將「測試」兩字字體顏色設置爲藍色 [attributedString addAttribute:NSForegroundColorAttributeName value:[NSColor blueColor] range:NSMakeRange(0, 2)]; //將「富文本」三個字字體顏色設置爲紅色 [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,因此能夠預見其實圖片附件屬性對應的內容應該是一個長度的字符 Printing description of attributedString: 測{ NSColor = "NSCalibratedRGBColorSpace 0 0 1 1"; NSFont = "\"LucidaGrande 24.00 pt. P [] (0x10051bfd0) fobj=0x101e687f0, spc=7.59\""; }{ NSAttachment = "<NSTextAttachment: 0x101e0c9c0> \"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下渲染實際所佔的區域(boundingRectWithSize:options:) 這些這裏就不介紹了。 從上面的代碼能夠看出其實Mac下的富文本的渲染並非很複雜,只要將Attributed String理解和使用好,其他的事情都交給NSTextView來作了,你徹底不用考慮其底層是如何取渲染的。可是在iOS平臺上就沒有這麼幸運了,雖然iOS從3。2開始也提供了NSAttributedString,可是並無相似NSTextView這樣的控件直接來渲染Attributed String。 這個時候你就得使用Core Text了。 編程
下面討論的Core Text相關編程都是特指在iOS平臺下。 Core Text是和Core Graphics配合使用的,通常是在UIView的drawRect方法中的Graphics Context上進行繪製的。 且Core Text真正負責繪製的是文本部分,圖片仍是須要本身去手動繪製,因此你必須關注不少繪製的細節部分。 app
在進入任何一個新的編程領域以前,咱們確定要先接觸相關的領域模型的知識。好比你軟件是進行科學計算的,那麼你就必須理解大量的數學原理;若是你的軟件是搞銀行系統,那麼你就得事先了解相關的銀行的業務知識。這些都是不可避免的事情。一般狀況下領域知識具備較高的通用性。但在特定的環境下,某些知識點也會被特殊處理。 Core Text是用來進行文字精細排版的,因此瞭解文字相關的知識也不可避免。 編程語言
排版系統中文本顯示的一個重要的過程就是字符到字形的轉換,字符是信息自己的元素,而字形是字符的圖形表徵,字符還會有其它表徵好比發音。 字符在計算機中其實就是一個編碼,某個字符集中的編碼,好比Unicode字符集,就囊括了大都數存在的字符。 而字形則是圖形,通常都存儲在字體文件中,字形也有它的編碼,也就是它在字體中的索引。 一個字符能夠對應多個字形(不一樣的字體,或者同種字體的不一樣樣式:粗體斜體等);多個字符也可能對應一個字形,好比字符的連寫( Ligatures)。
Roman Ligatures 編輯器
下面就來詳情看看字形的各個參數也就是所謂的字形度量Glyph Metrics 函數
一些Metrics專業知識還能夠參考Free Type的文檔 Glyph metrics,其實iOS就是使用Free Type庫來進行字體渲染的。 測試
以上圖片和部分概念來自蘋果文檔 Querying Font Metrics ,Text Layout 字體
首先不得不說 蘋果編程中的座標系花樣百出,常常讓開發者措手不及。 傳統的Mac中的座標系的原點在左下角,好比NSView默認的座標系,原點就在左下角。但Mac中有些View爲了其實現的便捷將原點變換到左上角,像NSTableView的座標系座標原點就在左上角。iOS UIKit的UIView的座標系原點在左上角。
往底層看,Core Graphics的context使用的座標系的原點是在左下角。而在iOS中的底層界面繪製就是經過Core Graphics進行的,那麼座標系列是如何變換的呢? 在UIView的drawRect方法中咱們能夠經過UIGraphicsGetCurrentContext()來得到當前的Graphics Context。drawRect方法在被調用前,這個Graphics Context被建立和配置好,你只管使用即是。若是你細心,經過CGContextGetCTM(CGContextRef c)能夠看到其返回的值並非CGAffineTransformIdentity,經過打印出來看到值爲 ui
Printing description of contextCTM: (CGAffineTransform) contextCTM = { a = 1 b = 0 c = 0 d = -1 tx = 0 ty = 460 }
這是非retina分辨率下的結果,若是是若是是retina上面的a,d,ty的值將會乘2,若是是iPhone 5,ty的值會再大些。 可是做用都是同樣的就是將上下文空間座標系進行了flip,使得本來左下角原點變到左上角,y軸正方向也變換成向下。 編碼
上面說了一大堆,下面進入正題,Core Text一開始即是定位於桌面的排版系統,使用了傳統的原點在左下角的座標系,因此它在繪製文本的時候都是參照左下角的原點進行繪製的。 可是iOS的UIView的drawRect方法的context被作了次flip,若是你啥也不作處理,直接在這個context上進行Core Text繪製,你會發現文字是鏡像且上下顛倒。
因此在UIView的drawRect方法中的context上進行Core Text繪製以前須要對context進行一次Flip。
這裏再說起一個函數CGContextSetTextMatrix,它能夠用來爲每個顯示的字形單獨設置變形矩陣。
Core Foundation和Foundation中的有些數據類型只須要簡單的強制類型轉換就能夠互換使用,這類類型咱們叫他們爲Toll-Free Bridged Types。
CFMutableAttributedStringRef和NSMutableAttributedString就是其中的一對,Core Foundation的接口基本是C的接口,功能強大,可是使用起來沒有Foundation中提供的Objc的接口簡單好使,因此不少時候咱們可使用高層接口組織數據,而後將其傳給低層函數接口使用。
這節主要來看看Core Text繪製的一些細節問題了,首先是Core Text繪製的流程:
上面說了這麼多對也沒一個東西和圖片繪製有關係,其實吧,Core Text自己並不支持圖片繪製,圖片的繪製你還得經過Core Graphics來進行。只是Core Text能夠經過CTRun的設置爲你的圖片在文本繪製的過程當中留出適當的空間。這個設置就使用到CTRunDelegate了,看這個名字大概就能夠知道什麼意思了,CTRunDelegate做爲CTRun相關屬性或操做擴展的一個入口,使得咱們能夠對CTRun作一些自定義的行爲。爲圖片留位置的方法就是加入一個空白的CTRun,自定義其ascent,descent,width等參數,使得繪製文本的時候留下空白位置給相應的圖片。而後圖片在相應的空白位置上使用Core Graphics接口進行繪製。
使用CTRunDelegateCreate能夠建立一個CTRunDelegate,它接收兩個參數,一個是callbacks結構體,一個是全部callback調用的時候須要傳入的對象。 callbacks的結構體爲CTRunDelegateCallbacks,主要是包含一些回調函數,好比有返回當前run的ascent,descent,width這些值的回調函數,至於函數中如何鑑別當前是哪一個run,能夠在CTRunDelegateCreate的第二個參數來達到目的,由於CTRunDelegateCreate的第二個參數會做爲每個回調調用時的入參。
這裏使用Core Text實現一個和以前NSTextView顯示相似的圖文混排的例子。
直接貼上代碼你們體會下:
void RunDelegateDeallocCallback( void* refCon ){ } CGFloat RunDelegateGetAscentCallback( void *refCon ){ NSString *imageName = (NSString *)refCon; return [UIImage imageNamed:imageName].size.height; } CGFloat RunDelegateGetDescentCallback(void *refCon){ return 0; } CGFloat RunDelegateGetWidthCallback(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 )); GContextSetRGBFillColor (context, 0, 0, 1, .5); CGContextFillRect (context, CGRectMake (0, 200, 100, 200)); CGContextSetTextMatrix(context, CGAffineTransformIdentity);//設置字形變換矩陣爲CGAffineTransformIdentity,也就是說每個字形都不作圖形變換 CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height); CGContextConcatCTM(context, flipVertical);//將當前context的座標系進行flip NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc] initWithString:@"測試富文本顯示"] autorelease]; //爲全部文本設置字體 //[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])]; //將「測試」兩字字體顏色設置爲藍色 //[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)]; //將「富文本」三個字字體顏色設置爲紅色 //[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)]; //爲圖片設置CTRunDelegate,delegate決定留給圖片的空間大小 NSString *taobaoImageName = @"taobao.png"; CTRunDelegateCallbacks imageCallbacks; 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 (int i = 0; i < CFArrayGetCount(lines); i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CGFloat lineAscent; CGFloat lineDescent; CGFloat lineLeading; CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading); CFArrayRef runs = CTLineGetGlyphRuns(line); for (int j = 0; j < CFArrayGetCount(runs); 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); }