Core Text 入门
本文所涉及的代碼你可以在這里下載到?https://github.com/kejinlu/CTTest,包含兩個項目,一個Mac的NSTextView的測試項目,一個iOS的Core Text的測試項目,轉載自:http://geeklu.com/2013/03/core-text/
NSTextView和Attribued String
第一次接觸蘋果系的富文本編程是在寫Mac平臺上的一個輸入框的時候,輸入框中的文字可以設置各種樣式,并可以在文字中間插入圖片,好在Mac的AppKit中提供了NSTextView這個支持富文本編輯器控件。此控件背后是通過什么方式來描述富文本的呢?答案是NSAttributedString,很多編程語言都提供了AttributedString的概念。NSAttributedString比NSString多了一個Attribute的概念,一個NSAttributedString的對象包含很多的屬性,每一個屬性都有其對應的字符區域,在這里是使用NSRange來進行描述的。下面是一個NSTextView顯示富文本的例子
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
下面討論的Core Text相關編程都是特指在iOS平臺下。 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,通過打印出來看到值為
這是非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顯示類似的圖文混排的例子。
直接貼上代碼大家體會下:
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 ));CGContextSetRGBFillColor (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的坐標系進行flipNSMutableAttributedString *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;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 (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); }轉載于:https://www.cnblogs.com/zsw-1993/p/4879542.html
總結
以上是生活随笔為你收集整理的Core Text 入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面包屑 CSS
- 下一篇: 系统性能检测工具之lsof