CoreText入門知識

很久以前寫的文章搬到這里來放著。
iOS開發(fā)中經(jīng)常會遇到做一些文字排版的需求,文字圖片混排的需求蛾绎,在iOS7 以前一般都使用CoreText來處理這樣的需求,iOS7之后多了一個TextKit 可以選擇鸦列,當(dāng)然TextKit是對CoreText的封裝租冠。

CoreText 是用于處理文字和字體的底層技術(shù),它直接和Core Graphics交互薯嗤;Core Graphics能夠直接處理字體和字形顽爹,將文字渲染到界面上,它是基礎(chǔ)庫中唯一能夠處理字形的模塊骆姐。


A3F7B81C-C370-4268-90E5-C1D1491F48A4.png

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

排版系統(tǒng)中文本顯示的一個重要的過程就是字符到字形的轉(zhuǎn)換镜粤,字符是信息本身的元素,而字形是字符的圖形表現(xiàn)玻褪,字符還會有其它表征比如發(fā)音肉渴。 字符在計算機中其實就是一個編碼,某個字符集中的編碼带射,比如Unicode字符集同规,就囊括了大多數(shù)存在的字符。 而字形則是圖形,一般都存儲在字體文件中券勺,字形也有它的編碼绪钥,也就是它在字體中的索引。 一個字符可以對應(yīng)多個字形(不同的字體关炼,或者同種字體的不同樣式:粗體斜體等)程腹;多個字符也可能對應(yīng)一個字形,比如字符的連寫( Ligatures)儒拂。


3845188204.gif

Roman Ligatures

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

262666560.gif
3439236923.gif
  • 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庫來進行字體渲染的寨典。

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躺枕,通過打印出來看到值為

CGContextRef context = UIGraphicsGetCurrentContext();    
CGAffineTransform transform = CGContextGetCTM(context);    
NSLog(@"%@",NSStringFromCGAffineTransform(transform));
[2, 0, 0, -2, 0, 202]//[a=2, b=0, c=0, d=-2, tx=0, ty=202]

Core Text一開始便是定位于桌面的排版系統(tǒng)服猪,使用了傳統(tǒng)的原點在左下角的坐標系供填,所以它在繪制文本的時候都是參照左下角的原點進行繪制的拐云。 但是iOS的UIView的drawRect方法的context被做了次flip,如果你啥也不做處理近她,直接在這個context上進行Core Text繪制叉瘩,你會發(fā)現(xiàn)文字是鏡像且上下顛倒。 如圖所示


2E20DF98-F59A-41B6-A903-F8154EBBCFA4.png

翻轉(zhuǎn)坐標:

CGContextSetTextMatrix(context, CGAffineTransformIdentity); 
CGContextTranslateCTM(context, 0, self.bounds.size.height); 
CGContextScaleCTM(context, 1.0, -1.0);

3.CoreText排版步驟

4F03378C-2AF3-4D1A-92CF-05B24BAF9322.png

使用CoreText進行文字排版的步驟:

1.準備文字也就是 NSMutableAttributedString/ NSAttributedString 對象粘捎。

2.根據(jù)NSMutableAttributedString創(chuàng)建CTFramesetter并初始化薇缅,同時系統(tǒng)自動的創(chuàng)建了CTTypesetter,CTTypesetter就是管理你的字體的類攒磨。它作為CTFrame對象的生產(chǎn)工廠泳桦,負責(zé)根據(jù)path生產(chǎn)對應(yīng)的CTFrame。

3.獲取CGPath娩缰,用于創(chuàng)建CTFrame灸撰。

4.根據(jù)CGPath產(chǎn)生對應(yīng)的CFFrame。

5.使用CTFrameDraw(ctFrame, context);進行繪制文本信息拼坎。
CTFrame結(jié)構(gòu):

1D425790-3FD5-468B-917B-D1B83697445C.png

CTFrame浮毯、CTLine、CTRun三者之間的關(guān)系:

CTFrame: 就好比一篇文章泰鸡,一篇文章會包含多個顯示的行

CTLine: 就是上面所說的文章中的每一行债蓝,而每一行又包含多個塊

CTRun: 就是一行中的很多的塊,而塊是指 一組共享 相同屬性 的字體 的 集合
Code:

- (void)drawRect:(CGRect)rect {    
    CGContextRef context = UIGraphicsGetCurrentContext();
    //每一個字形都不做圖形變換    
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);            
    //翻轉(zhuǎn)坐標    
    CGContextTranslateCTM(context, 0, self.bounds.size.height);    
    CGContextScaleCTM(context, 1.0, -1.0);

    //測試文字    
    NSMutableAttributedString *astring = [[NSMutableAttributedString alloc] initWithString:@"測試富文本顯示"]; 
    //設(shè)置astring 樣式    
    [astring addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:18] range:NSMakeRange(0, 2)];    
    [astring addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(2, 2)];

    //繪制文本    
    //初始化ctFramesetter    
    CTFramesetterRef ctFramesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)astring);            //創(chuàng)建path    
    CGMutablePathRef path = CGPathCreateMutable();    
    CGRect bounds = CGRectMake(0.0, 0.0, self.bounds.size.width, self.bounds.size.height);    
    CGPathAddRect(path, NULL, bounds);    //ctFramesetter 根據(jù)path產(chǎn)生ctFrame    
    CTFrameRef ctFrame = CTFramesetterCreateFrame(ctFramesetter,CFRangeMake(0, 0), path, NULL);    //繪制ctFrame    
    CTFrameDraw(ctFrame, context);
}

4.圖文混排


184A51FC-C5EC-4DB0-BE30-2D37A4E0FED0.png

圖文混排需要Core Text是和Core Graphics配合使用的盛龄,一般是在UIView的drawRect方法中的Graphics Context上進行繪制的饰迹。 且Core Text真正負責(zé)繪制的是文本部分,圖片還是需要自己去手動繪制余舶,所以你必須關(guān)注很多繪制的細節(jié)部分蹦锋。只是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>

void RunDelegateDeallocCallback( void* refCon )
{
}


CGFloat RunDelegateGetAscentCallback( void *refCon )
{
    NSString *imageName = (__bridge NSString *) refCon; return([UIImage imageNamed:imageName].size.height);
}


CGFloat RunDelegateGetDescentCallback( void *refCon )
{
    return(0);
}


CGFloat RunDelegateGetWidthCallback( void *refCon )
{
    NSString *imageName = (__bridge NSString *) refCon; return([UIImage imageNamed:imageName].size.width);
}

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    //每一個字形都不做圖形變換
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    //翻轉(zhuǎn)坐標
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    
    //測試文字
    NSMutableAttributedString *astring = [[NSMutableAttributedString alloc] initWithString:@"測試富文本顯示"];
    
    //設(shè)置astring 樣式
    [astring addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:18] range:NSMakeRange(0, 2)];
    [astring addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(2, 2)];

    //圖片名稱
    NSString *imgName = @"009@2x.png";
    //CTRunDelegateCallbacks用于占位置卿拴,給圖片預(yù)留大小
    CTRunDelegateCallbacks imageCallbacks;
    imageCallbacks.version = kCTRunDelegateVersion1;
    imageCallbacks.dealloc = RunDelegateDeallocCallback;
    imageCallbacks.getAscent = RunDelegateGetAscentCallback;
    imageCallbacks.getDescent = RunDelegateGetDescentCallback;
    imageCallbacks.getWidth = RunDelegateGetWidthCallback;
    CTRunDelegateRef ctRunDelegate = CTRunDelegateCreate(&imageCallbacks, (__bridge void * _Nullable)(imgName));
    NSMutableAttributedString *imageAttrString = [[NSMutableAttributedString alloc] initWithString:@" "];
    [imageAttrString addAttribute:(NSString *)kCTRunDelegateAttributeName value:(__bridge id)ctRunDelegate range:NSMakeRange(0, 1)];
    CFRelease(ctRunDelegate);
    //把圖片名字與所在的位置綁定类咧,方便后續(xù)繪制取出對應(yīng)圖片
    [imageAttrString addAttribute:@"imageName" value:imgName range:NSMakeRange(0, 1)];
    //圖片所在文字的位置
    [astring insertAttributedString:imageAttrString atIndex:1];

    //繪制文本
    CTFramesetterRef ctFramesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)astring);

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

    //繪制圖片,遍歷找到每一個CTRun 判斷是否有圖片履婉,然后在繪制出CTRun中對應(yīng)的圖片
    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;
        //獲取line的繪制矩形
        CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
        CFArrayRef runs = CTLineGetGlyphRuns(line);
        for (int j = 0; j < CFArrayGetCount(runs); j++) {
            CTRunRef run = CFArrayGetValueAtIndex(runs, j);
            CGFloat runAscent;
            CGFloat runDescent;
            CGPoint lineOrigin = lineOrigins[i];
            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, runRect.size.width, runAscent+runDescent);
            NSString *imageName = [attributes objectForKey:@"imageName"];
            if (imageName) {
                UIImage *img = [UIImage imageNamed:imageName];
                if (img) {
                    CGRect imageRect;
                    imageRect.size = img.size;
                    imageRect.origin.x = lineOrigin.x + runRect.origin.x;
                    imageRect.origin.y = lineOrigin.y;
                    CGContextDrawImage(context, imageRect, img.CGImage);
                }
            }
        }
    }
    CFRelease(ctFrame);
    CFRelease(path);
    CFRelease(ctFramesetter);
}
542A9C50-D0C1-4885-9770-373635C89AD8.png

參考資料:

http://geeklu.com/2013/03/core-text/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末真仲,一起剝皮案震驚了整個濱河市苏研,隨后出現(xiàn)的幾起案子歪沃,更是在濱河造成了極大的恐慌袱巨,老刑警劉巖适篙,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铁坎,死亡現(xiàn)場離奇詭異蜂奸,居然都是意外死亡,警方通過查閱死者的電腦和手機硬萍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進店門扩所,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人朴乖,你說我怎么就攤上這事祖屏。” “怎么了买羞?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵袁勺,是天一觀的道長。 經(jīng)常有香客問我畜普,道長期丰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任吃挑,我火速辦了婚禮钝荡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舶衬。我一直安慰自己埠通,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布逛犹。 她就那樣靜靜地躺著端辱,像睡著了一般梁剔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舞蔽,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天荣病,我揣著相機與錄音,去河邊找鬼喷鸽。 笑死众雷,一個胖子當(dāng)著我的面吹牛灸拍,可吹牛的內(nèi)容都是我干的做祝。 我是一名探鬼主播,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼鸡岗,長吁一口氣:“原來是場噩夢啊……” “哼混槐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起轩性,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤声登,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后揣苏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悯嗓,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年卸察,在試婚紗的時候發(fā)現(xiàn)自己被綠了脯厨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡坑质,死狀恐怖合武,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涡扼,我是刑警寧澤稼跳,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站吃沪,受9級特大地震影響汤善,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜票彪,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一萎津、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抹镊,春花似錦锉屈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遂黍。三九已至,卻和暖如春俊嗽,著一層夾襖步出監(jiān)牢的瞬間雾家,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工绍豁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芯咧,地道東北人。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓竹揍,卻偏偏與公主長得像敬飒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子芬位,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,937評論 2 361

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

  • 本文所涉及的代碼你可以在這里下載到https://github.com/kejinlu/CTTest无拗,包含兩個項目...
    eb99d15a673d閱讀 1,261評論 0 6
  • 1.iOS中的round、ceil昧碉、floor函數(shù)略解 round如果參數(shù)是小數(shù)英染,則求本身的四舍五入.ceil如果...
    K_Gopher閱讀 1,190評論 1 0
  • CoreText 框架中最常用的幾個類 CTFont CTFontCollection CTFontDescrip...
    神采飛揚_2015閱讀 1,058評論 1 11
  • blog.csdn.net CoreText實現(xiàn)圖文混排 - 博客頻道 CoreText實現(xiàn)圖文混排 也好久沒來寫...
    K_Gopher閱讀 607評論 0 0
  • iOS沒有現(xiàn)成的支持圖文混排的控件,而要用多個基礎(chǔ)控件組合拼成圖文混排這樣復(fù)雜的排版被饿,是件很苦逼的事情四康。對此的解決...
    清風(fēng)沐沐閱讀 675評論 0 2