CoreText 原理

CoreText

原理

為了方便使用闯袒,需要先創(chuàng)建一個(gè)自定義UIView惹挟,我們將在drawRect函數(shù)里使用CoreText搪花。

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];

    CGContextRef context = UIGraphicsGetCurrentContext();

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

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddEllipseInRect(path, NULL, self.bounds);

    NSAttributedString *attString = [[NSAttributedString alloc] initWithString:@"Hello World"];
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attString.length), path, NULL);

    CTFrameDraw(frame, context);

    CFRelease(frame);
    CGPathRelease(path);
    CGContextRelease(context);
}
  1. 我們首先創(chuàng)建了一個(gè)CGContextRef上下文搓谆。
  2. 因?yàn)镃oreText的坐標(biāo)系是以右下角為原點(diǎn)刁愿,所以我們將CoreText坐標(biāo)系翻轉(zhuǎn)一下绰寞。
  3. 創(chuàng)建了一個(gè)CGPath,來(lái)完成繪制文字的區(qū)域铣口。CoreText支持矩形滤钱、環(huán)形。因?yàn)橛谜麄€(gè)View來(lái)做顯示區(qū)域脑题,所以我們通過(guò)self.bounds來(lái)創(chuàng)建CGPath件缸。
  4. 在CoreText我們要渲染的文字需要用NSAttributedString來(lái)創(chuàng)建,它允許你對(duì)文字顏色旭蠕、字體停团、大小設(shè)置不同的樣式。
  5. CTFramesetterRef是CoreText非常重要的一個(gè)類掏熬,他管理了你得字體和文本渲染塊(CTFrame)佑稠。最簡(jiǎn)單的創(chuàng)建就是通過(guò)NSAttributedString來(lái)創(chuàng)建一個(gè)CTFramesetterRef。然后通過(guò)剛創(chuàng)建的CTFramesetterRef來(lái)創(chuàng)建一個(gè)CTFrameRef旗芬。CTFramesetterCreateFrame的參數(shù)分別是frameSetter舌胶、將要顯示文字的Range(這里?長(zhǎng)度attString.length)、文本要顯示的區(qū)域(剛剛創(chuàng)建的path)和frameAttributes(可以為空)疮丛。
  6. 通過(guò)CTFrameDraw在給定的context里繪制frame幔嫂。
  7. 最后不要忘了清理資源。

CoreText對(duì)象模型

Unknown.jpg

在CTFrame內(nèi)部是由多個(gè)CTline組成誊薄,每行CTline又是由多個(gè)CTRun組成
每個(gè)CTRun代表一組風(fēng)格一致的文本(CTline和CTRun的創(chuàng)建不需要我們管理)
在CTRun中我們可以設(shè)置代理來(lái)指定繪制此組文本的寬高和排列方式等信息

我們通過(guò)NSAttributedString創(chuàng)建一個(gè)CTFramesetter履恩,這時(shí)候會(huì)自動(dòng)創(chuàng)建一個(gè) CTTypesetter實(shí)例,它負(fù)責(zé)管理字體,下面通過(guò)CTFramesetter來(lái)創(chuàng)建一個(gè)或多個(gè)frame來(lái)渲染文字呢蔫。然后Core Text會(huì)根據(jù)frame的大小自動(dòng)創(chuàng)建CTLine(每行對(duì)應(yīng)一個(gè)CTLine)和CTRun(相同格式的一個(gè)或多個(gè)相鄰字符組成一個(gè)CTRun)切心。
舉例來(lái)說(shuō),Core Text將創(chuàng)建一個(gè)CTRun來(lái)繪制一些紅色文字片吊,然后創(chuàng)建一個(gè)CTRun來(lái)繪制純文本绽昏,然后再創(chuàng)建一個(gè)CTRun來(lái)繪制加粗文字等等。要注意俏脊,你不需要自己創(chuàng)建CTRun全谤,Core Text將根據(jù)NSAttributedString的屬性來(lái)自動(dòng)創(chuàng)建CTRun。每個(gè)CTRun對(duì)象對(duì)應(yīng)不同的屬性爷贫,正因此认然,你可以自由的控制字體补憾、顏色、字間距等等信息卷员。
#import "CoreTextData.h"

@interface CoreTextData : NSObject
/** 文本繪制的區(qū)域大小 */
@property (nonatomic, assign) CTFrameRef ctFrame;
/** 文本繪制區(qū)域高度 */
@property (nonatomic, assign) CGFloat height;
/** 文本中存儲(chǔ)圖片信息數(shù)組 */
@property (nonatomic, strong) NSMutableArray *imageArray;
/** 文本中存儲(chǔ)鏈接信息數(shù)組 */
@property (nonatomic, strong) NSMutableArray *linkArray;
@end
@implementation CoreTextData

    - (void)setCtFrame:(CTFrameRef)ctFrame {
        if (_ctFrame != ctFrame) {
            if (_ctFrame != nil) {
                CFRelease(_ctFrame);
            }
            CFRetain(ctFrame);
            _ctFrame = ctFrame;
        }
    }

- (void)dealloc {
    if (_ctFrame != nil) {
        CFRelease(_ctFrame);
        _ctFrame = nil;
    }
}

- (void)setImageArray:(NSArray *)imageArray {
    _imageArray = imageArray;
    [self fillImagePosition];
}

- (void)fillImagePosition {
    if (self.imageArray.count == 0) {
        return;
    }
    
    // 此處利用CTRun代理設(shè)置一個(gè)空白的字符給定寬高余蟹,最后在利用CGContextDrawImage將其繪制
    
    // 獲取CTFrame中所有的line
    NSArray *lines = (NSArray *)CTFrameGetLines(self.ctFrame);
    NSUInteger lineCount = [lines count];
    // 利用CGPoint數(shù)組獲取所有l(wèi)ine的起始坐標(biāo)
    CGPoint lineOrigins[lineCount];
    CTFrameGetLineOrigins(self.ctFrame, CFRangeMake(0, 0), lineOrigins);

    // 獲取圖片數(shù)組中第一個(gè)圖片信息
    int imgIndex = 0;
    CoreTextImageData * imageData = self.imageArray[0];

    for (int i = 0; i < lineCount; ++i) {
        // 如果不存在圖片則返回
        if (imageData == nil) {
            break;
        }
        // 存在圖片信息則獲取圖片具體位置信息
        // 獲取每行信息
        CTLineRef line = (__bridge CTLineRef)lines[i];
        // 得到每行的CTRun信息,并遍歷
        NSArray * runObjArray = (NSArray *)CTLineGetGlyphRuns(line);
        for (id runObj in runObjArray) {
        CTRunRef run = (__bridge CTRunRef)runObj;
            NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run);
            // 獲取CTRun的代理信息子刮,若無(wú)代理信息則直接進(jìn)入下次循環(huán)
            CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName];
            if (delegate == nil) {
                continue;
            }
        
        // 若有代理信息威酒,判斷代理信息是否為字典,不是直接進(jìn)入下次循環(huán)
        NSDictionary * metaDic = CTRunDelegateGetRefCon(delegate);
        if (![metaDic isKindOfClass:[NSDictionary class]]) {
            continue;
        }
        
        CGRect runBounds;
        CGFloat ascent;
        CGFloat descent;
        // 找到CTRunDelegate中的寬度并給上升和下降高度賦值
        runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
        runBounds.size.height = ascent + descent;
        
        // 獲取CTRun在x上的偏移量
        CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
        // 起點(diǎn)坐標(biāo)
        runBounds.origin.x = lineOrigins[i].x + xOffset;
        runBounds.origin.y = lineOrigins[i].y;
        runBounds.origin.y -= descent;
        
        // 獲取路徑挺峡,并利用路徑獲取繪制視圖的Rect
        CGPathRef pathRef = CTFrameGetPath(self.ctFrame);
        CGRect colRect = CGPathGetBoundingBox(pathRef);
        
        CGRect delegateBounds = CGRectOffset(runBounds, colRect.origin.x, colRect.origin.y);
        
        // 保存圖片位置信息
        imageData.imagePosition = delegateBounds;
        imgIndex++;
        if (imgIndex == self.imageArray.count) {
            imageData = nil;
            break;
        } else {
               imageData = self.imageArray[imgIndex];
        }
      }
   }
}
@end

#import "CoreTextImageData.h"
@interface CoreTextImageData : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) CGRect imagePosition;
@end




#import "CoreTextLinkData.h"

@implementation CoreTextLinkData
+ (CoreTextLinkData *)touchLinkInView:(UIView *)view atPoint:(CGPoint)point data:(CoreTextData *)data {

    CTFrameRef ctFrame = data.ctFrame;
    CFArrayRef lines = CTFrameGetLines(ctFrame);
    if (lines == nil) {
        return nil;
    }

    CFIndex linesCount = CFArrayGetCount(lines);
    CoreTextLinkData *linkdata = nil;

    CGPoint linesOrigins[linesCount];
    CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), linesOrigins);

    //由于CoreText和UIKit坐標(biāo)系不同所以要做個(gè)對(duì)應(yīng)轉(zhuǎn)換
    CGAffineTransform transform = CGAffineTransformMakeTranslation(0, view.bounds.size.height);
    transform = CGAffineTransformScale(transform, 1, -1);

    for (int i = 0; i < linesCount; i ++) {
        CGPoint linePoint = linesOrigins[i];
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        //獲取當(dāng)前行的rect信息
        CGRect flippedRect = [self getLineBounds:line point:linePoint];
        //將CoreText坐標(biāo)轉(zhuǎn)換為UIKit坐標(biāo)
        CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
        //判斷點(diǎn)是否在Rect當(dāng)中
        if (CGRectContainsPoint(rect, point)) {
            //獲取點(diǎn)在line行中的位置
            CGPoint relativePoint = CGPointMake(point.x - CGRectGetMinX(rect), point.y - CGRectGetMinY(rect));
            //獲取點(diǎn)中字符在line中的位置(在屬性文字中是第幾個(gè)字)
            CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);
            //判斷此字符是否在鏈接屬性文字當(dāng)中
            linkdata = [self linkAtIndex:idx linkArray:data.linkArray];
            break;
        }
    }
    return linkdata;
}

+ (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint)point {
    //配置line行的位置信息
    CGFloat ascent = 0;
    CGFloat descent = 0;
    CGFloat leading = 0;
    //在獲取line行的寬度信息的同時(shí)得到其他信息
    CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
    CGFloat height = ascent + descent;
    return CGRectMake(point.x, point.y, width, height);
}

+ (CoreTextLinkData *)linkAtIndex:(CFIndex)i linkArray:(NSArray *)linkArray {
    CoreTextLinkData *linkdata = nil;
    for (CoreTextLinkData *data in linkArray) {
        if (NSLocationInRange(i, data.range)) {
            linkdata = data;
            break;
        }
    }
    return linkdata;
}
@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末葵孤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子橱赠,更是在濱河造成了極大的恐慌尤仍,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狭姨,死亡現(xiàn)場(chǎng)離奇詭異宰啦,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)饼拍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門赡模,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人师抄,你說(shuō)我怎么就攤上這事漓柑。” “怎么了叨吮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵辆布,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我茶鉴,道長(zhǎng)锋玲,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任涵叮,我火速辦了婚禮惭蹂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘围肥。我一直安慰自己剿干,他們只是感情好蜂怎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布穆刻。 她就那樣靜靜地躺著,像睡著了一般杠步。 火紅的嫁衣襯著肌膚如雪氢伟。 梳的紋絲不亂的頭發(fā)上榜轿,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音朵锣,去河邊找鬼谬盐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诚些,可吹牛的內(nèi)容都是我干的飞傀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼诬烹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼砸烦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起绞吁,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤幢痘,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后家破,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體颜说,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年汰聋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了门粪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烹困,死狀恐怖庄拇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情韭邓,我是刑警寧澤措近,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站女淑,受9級(jí)特大地震影響瞭郑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸭你,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一屈张、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袱巨,春花似錦阁谆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嫉入,卻和暖如春焰盗,著一層夾襖步出監(jiān)牢的瞬間璧尸,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工熬拒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爷光,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓澎粟,卻偏偏與公主長(zhǎng)得像蛀序,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子活烙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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

  • 整理中... 文本布局 TextLayout = Glyphs + Locations參考:活字印刷 Glyphs...
    DBreak閱讀 15,570評(píng)論 17 71
  • CoreText是一個(gè)進(jìn)階的比較底層的布局文本和處理字體的技術(shù)哼拔,CoreText API在OS X v10.5 和...
    smalldu閱讀 13,445評(píng)論 18 129
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件瓣颅、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評(píng)論 4 62
  • “忍倦逐,是不是已經(jīng)習(xí)慣了?” “我不是為了習(xí)慣才忍宫补,我忍著檬姥,只是為了有天能夠擺脫這些人,擺脫這樣的生活粉怕〗∶瘢”
    聞夢(mèng)之耳閱讀 137評(píng)論 0 0
  • 天才交援少女,嗽嗽贫贝,是天才麻將少女才對(duì)!現(xiàn)在天才麻將少女還在更新當(dāng)中(一般是月更)秉犹,上次也出現(xiàn)過(guò)真人版的消息,而這...
    緣葉二次元閱讀 582評(píng)論 0 0