Core Text

 Core Text是和Core Graphics配合使用的,一般是在UIView的drawRect方法中的Graphics Context上進行繪製的。 且Core Text真正負責繪製的是文本部分,圖片還是需要自己去手動繪製,所以你必須關注很多繪製的細節部分。

一.Core Text知識準備

在進入任何一個新的編程領域之前,我們肯定要先接觸相關的領域模型的知識。比如你軟件是進行科學計算的,那麼你就必須理解大量的數學原理;如果你的軟件是搞銀行系統,那麼你就得事先了解相關的銀行的業務知識。這些都是不可避免的事情。通常情況下領域知識具有較高的通用性。但在特定的環境下,某些知識點也會被特殊處理。 Core Text是用來進行文字精細排版的,所以瞭解文字相關的知識也不可避免。

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

排版系統中文本顯示的一個重要的過程就是字符到字形的轉換,字符是信息本身的元素,而字形是字符的圖形表徵,字符還會有其它表徵比如發音。 字符在計算機中其實就是一個編碼,某個字符集中的編碼,比如Unicode字符集,就囊括了大都數存在的字符。 而字形則是圖形,一般都存儲在字體文件中,字形也有它的編碼,也就是它在字體中的索引。 一個字符可以對應多個字形(不同的字體,或者同種字體的不同樣式:粗體斜體等);多個字符也可能對應一個字形,比如字符的連寫( Ligatures)。 
 
Roman Ligatures

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

 

  • bounding box(邊界框 bbox),這是一個假想的框子,它儘可能緊密的裝入字形。
  • baseline(基線),一條假想的線,一行上的字形都以此線作爲上下位置的參考,在這條線的左側存在一個點叫做基線的原點,
  • ascent(上行高度)從原點到字體中最高(這裏的高深都是以基線爲參照線的)的字形的頂部的距離,ascent是一個正值
  • descent(下行高度)從原點到字體中最深的字形底部的距離,descent是一個負值(比如一個字體原點到最深的字形的底部的距離爲2,那麼descent就爲-2)
  • linegap(行距),linegap也可以稱作leading(其實準確點講應該叫做External leading),行高lineHeight則可以通過 ascent + |descent| + linegap 來計算。

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

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

2.座標系

首先不得不說 蘋果編程中的座標系花樣百出,經常讓開發者措手不及。 傳統的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,通過打印出來看到值爲

[html] view plaincopy
  1. Printing description of contextCTM:  
  2. (CGAffineTransform) contextCTM = {  
  3.         a = 1  
  4.         b = 0  
  5.         c = 0  
  6.         d = -1  
  7.         tx = 0  
  8.         ty = 460  
  9. }         

這是非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,它可以用來爲每一個顯示的字形單獨設置變形矩陣。

3.NSMutableAttributedString 和 CFMutableAttributedStringRef

Core Foundation和Foundation中的有些數據類型只需要簡單的強制類型轉換就可以互換使用,這類類型我們叫他們爲Toll-Free Bridged Types。 
CFMutableAttributedStringRef和NSMutableAttributedString就是其中的一對,Core Foundation的接口基本是C的接口,功能強大,但是使用起來沒有Foundation中提供的Objc的接口簡單好使,所以很多時候我們可以使用高層接口組織數據,然後將其傳給低層函數接口使用。

二.Core Text對象模型

這節主要來看看Core Text繪製的一些細節問題了,首先是Core Text繪製的流程: 

  • framesetter framesetter對應的類型是 CTFramesetter,通過CFAttributedString進行初始化,它作爲CTFrame對象的生產工廠,負責根據path生產對應的CTFrame
  • CTFrame CTFrame是可以通過CTFrameDraw函數直接繪製到context上的,當然你可以在繪製之前,操作CTFrame中的CTLine,進行一些參數的微調
  • CTLine 可以看做Core Text繪製中的一行的對象 通過它可以獲得當前行的line ascent,line descent ,line leading,還可以獲得Line下的所有Glyph Runs
  • CTRun 或者叫做 Glyph Run,是一組共享想相同attributes(屬性)的字形的集合體

上面說了這麼多對也沒一個東西和圖片繪製有關係,其實吧,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實戰

這裏使用Core Text實現一個和之前NSTextView顯示類似的圖文混排的例子。

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

[html] view plaincopy
  1. void RunDelegateDeallocCallback( void* refCon ){  
  2.       
  3. }  
  4.   
  5. CGFloat RunDelegateGetAscentCallback( void *refCon ){  
  6.     NSString *imageName = (NSString *)refCon;  
  7.     return [UIImage imageNamed:imageName].size.height;  
  8. }  
  9.   
  10. CGFloat RunDelegateGetDescentCallback(void *refCon){  
  11.     return 0;  
  12. }  
  13.   
  14. CGFloat RunDelegateGetWidthCallback(void *refCon){  
  15.     NSString *imageName = (NSString *)refCon;  
  16.     return [UIImage imageNamed:imageName].size.width;  
  17. }  
  18.   
  19.   
  20. - (void)drawRect:(CGRect)rect  
  21. {  
  22.     CGContextRef context = UIGraphicsGetCurrentContext();  
  23.       
  24.     //這四行代碼只是簡單測試drawRect中context的座標系  
  25.     CGContextSetRGBFillColor (context, 1, 0, 0, 1);  
  26.     CGContextFillRect (context, CGRectMake (0, 200, 200, 100 ));  
  27.     CGContextSetRGBFillColor (context, 0, 0, 1, .5);  
  28.     CGContextFillRect (context, CGRectMake (0, 200, 100, 200));  
  29.       
  30.     CGContextSetTextMatrix(context, CGAffineTransformIdentity);//設置字形變換矩陣爲CGAffineTransformIdentity,也就是說每一個字形都不做圖形變換  
  31.       
  32.     CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height);  
  33.     CGContextConcatCTM(context, flipVertical);//將當前context的座標系進行flip  
  34.       
  35.     NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc] initWithString:@"測試富文本顯示"] autorelease];  
  36.       
  37.     //爲所有文本設置字體  
  38.     //[attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:24] range:NSMakeRange(0, [attributedString length])]; // 6.0+  
  39.     UIFont *font = [UIFont systemFontOfSize:24];  
  40.     CTFontRef fontRef = CTFontCreateWithName((CFStringRef)font.fontName, font.pointSize, NULL);  
  41.     [attributedString addAttribute:(NSString *)kCTFontAttributeName value:(id)fontRef range:NSMakeRange(0, [attributedString length])];  
  42.       
  43.     //將“測試”兩字字體顏色設置爲藍色  
  44.     //[attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, 2)]; //6.0+  
  45.     [attributedString addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)[UIColor blueColor].CGColor range:NSMakeRange(0, 2)];  
  46.       
  47.     //將“富文本”三個字字體顏色設置爲紅色  
  48.     //[attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(2, 3)]; //6.0+  
  49.     [attributedString addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)[UIColor redColor].CGColor range:NSMakeRange(2, 3)];  
  50.       
  51.       
  52.     //爲圖片設置CTRunDelegate,delegate決定留給圖片的空間大小  
  53.     NSString *taobaoImageName = @"taobao.png";  
  54.     CTRunDelegateCallbacks imageCallbacks;  
  55.     imageCallbacks.version = kCTRunDelegateVersion1;  
  56.     imageCallbacks.dealloc = RunDelegateDeallocCallback;  
  57.     imageCallbacks.getAscent = RunDelegateGetAscentCallback;  
  58.     imageCallbacks.getDescent = RunDelegateGetDescentCallback;  
  59.     imageCallbacks.getWidth = RunDelegateGetWidthCallback;  
  60.     CTRunDelegateRef runDelegate = CTRunDelegateCreate(&imageCallbacks, taobaoImageName);  
  61.     NSMutableAttributedString *imageAttributedString = [[NSMutableAttributedString alloc] initWithString:@" "];//空格用於給圖片留位置  
  62.     [imageAttributedString addAttribute:(NSString *)kCTRunDelegateAttributeName value:(id)runDelegate range:NSMakeRange(0, 1)];  
  63.     CFRelease(runDelegate);  
  64.       
  65.     [imageAttributedString addAttribute:@"imageName" value:taobaoImageName range:NSMakeRange(0, 1)];  
  66.       
  67.     [attributedString insertAttributedString:imageAttributedString atIndex:1];  
  68.       
  69.     CTFramesetterRef ctFramesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)attributedString);  
  70.       
  71.     CGMutablePathRef path = CGPathCreateMutable();  
  72.     CGRect bounds = CGRectMake(0.0, 0.0, self.bounds.size.width, self.bounds.size.height);  
  73.     CGPathAddRect(path, NULL, bounds);  
  74.       
  75.     CTFrameRef ctFrame = CTFramesetterCreateFrame(ctFramesetter,CFRangeMake(0, 0), path, NULL);  
  76.     CTFrameDraw(ctFrame, context);  
  77.       
  78.     CFArrayRef lines = CTFrameGetLines(ctFrame);  
  79.     CGPoint lineOrigins[CFArrayGetCount(lines)];  
  80.     CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);  
  81.       
  82.     for (int i = 0; i < CFArrayGetCount(lines); i++) {  
  83.         CTLineRef line = CFArrayGetValueAtIndex(lines, i);  
  84.         CGFloat lineAscent;  
  85.         CGFloat lineDescent;  
  86.         CGFloat lineLeading;  
  87.         CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);  
  88.           
  89.         CFArrayRef runs = CTLineGetGlyphRuns(line);  
  90.         for (int j = 0; j < CFArrayGetCount(runs); j++) {  
  91.             CGFloat runAscent;  
  92.             CGFloat runDescent;  
  93.             CGPoint lineOrigin = lineOrigins[i];  
  94.             CTRunRef run = CFArrayGetValueAtIndex(runs, j);  
  95.             NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);  
  96.             CGRect runRect;  
  97.             runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, NULL);  
  98.               
  99.             runRect=CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL), lineOrigin.y - runDescent, runRect.size.width, runAscent + runDescent);  
  100.               
  101.             NSString *imageName = [attributes objectForKey:@"imageName"];  
  102.             //圖片渲染邏輯  
  103.             if (imageName) {  
  104.                 UIImage *image = [UIImage imageNamed:imageName];  
  105.                 if (image) {  
  106.                     CGRect imageDrawRect;  
  107.                     imageDrawRect.size = image.size;  
  108.                     imageDrawRect.origin.x = runRect.origin.x + lineOrigin.x;  
  109.                     imageDrawRect.origin.y = lineOrigin.y;  
  110.                     CGContextDrawImage(context, imageDrawRect, image.CGImage);  
  111.                 }  
  112.             }  
  113.         }  
  114.     }  
  115.       
  116.     CFRelease(ctFrame);  
  117.     CFRelease(path);  
  118.     CFRelease(ctFramesetter);  
  119. }  
發佈了75 篇原創文章 · 獲贊 6 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章