iOS閱讀器實踐系列(一)coretext純文字排版基礎,ioscoretext

來源:互聯網
上載者:User

iOS閱讀器實踐系列(一)coretext純文字排版基礎,ioscoretext

前言:之前做了公司閱讀類的App,最近有時間來寫一下閱讀部分的實現過程,供梳理邏輯,計劃會寫一個系列希望能涉及到盡量多的方面與細節,歡迎大家交流、吐槽、拍磚,共同進步。

閱讀的排版用的是coretext,這篇介紹用coretext實現基本的排版功能。

關於coretext的實現原理,可以查看文檔或其他資料,這裡就不介紹了,只介紹如何應用coretext來實現一個簡單的文本排版功能。

因為coretext是離屏排版的,即在將內容渲染到螢幕之前,內容的排版工作的已經完成了。

排版過程大致過程分為 步:

一、由原始文本資料和需要的相關配置來得到屬性字串。

二、由屬性字串得到CTFramesetter

三、由CTFramesetter和繪製地區得到CTFrame

四、最後將CTFrame渲染到視圖的上下文中

 

1、由原始文本資料和需要的相關配置來得到屬性字串

這一部最關鍵的是得到相關配置,這些配置可能包括文本對齊、段收尾縮排、行高等,下面是一些相關配置屬性:

@interface CTFrameParserConfigure : NSObject@property (nonatomic, assign) CGFloat frameWidth;@property (nonatomic, assign) CGFloat frameHeight;//字型屬性@property (nonatomic, assign) CGFloat wordSpace;@property (nonatomic, strong) UIColor *textColor;@property (nonatomic, strong) NSString *fontName;@property (nonatomic, assign) CGFloat fontSize;//段落屬性@property (nonatomic, assign) CGFloat lineSpace;@property (nonatomic, assign) CTTextAlignment textAlignment; //文本對齊模式@property (nonatomic, assign) CGFloat firstlineHeadIndent; //段首行縮排@property (nonatomic, assign) CGFloat headIndent;  //段左側整體縮排@property (nonatomic, assign) CGFloat tailIndent;  //段尾縮排@property (nonatomic, assign) CTLineBreakMode lineBreakMode;  //換行模式@property (nonatomic, assign) CGFloat lineHeightMutiple; //行高倍數器(它的值表示原行高的倍數)@property (nonatomic, assign) CGFloat maxLineHeight; //最大行高限制(0表示無限制,是非負的,行高不能超過此值)@property (nonatomic, assign) CGFloat minLineHeight;  //最小行高限制@property (nonatomic,assign) CGFloat paragraphBeforeSpace;  //段前間距(相對上一段加上的間距)@property (nonatomic, assign) CGFloat paragraphAfterSpace; //段尾間距(相對下一段加上的間距)@property (nonatomic, assign) CTWritingDirection writeDirection;  //書寫方向@property (nonatomic, assign) CGFloat lineSpacingAdjustment;  //The space in points added between lines within the paragraph (commonly known as leading).@end

接下來我們要利用這些屬性,產生我們需要的配置,在我們根據我們的需要給這些屬性賦值以後,利用下面的方法來得到我們需要的配置:

//返迴文本所有屬性的集合(以字典形式),包括字型、段落等- (NSDictionary *)attributesWithConfig:(CTFrameParserConfigure *)config{    //段落屬性    CGFloat lineSpacing = config.lineSpace;    CGFloat firstLineIndent = config.firstlineHeadIndent;    CGFloat lineIndent = config.headIndent;    CGFloat tailIndent = config.tailIndent;    CTLineBreakMode lineBreakMode = config.lineBreakMode;    CGFloat lineHeightMutiple = config.lineHeightMutiple;    CGFloat paragraphBeforeSpace = config.paragraphBeforeSpace;    CGFloat paragraphAfterSpace = config.paragraphAfterSpace;    CTWritingDirection writeDirect = config.writeDirection;    CTTextAlignment textAlignment = config.textAlignment;    const CFIndex kNumberOfSettings = 13;    CTParagraphStyleSetting paragraphSettings[kNumberOfSettings] = {        { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing },        { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing },        { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing },        { kCTParagraphStyleSpecifierAlignment, sizeof(textAlignment), &textAlignment },        { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(firstLineIndent), &firstLineIndent },        { kCTParagraphStyleSpecifierHeadIndent, sizeof(lineIndent), &lineIndent },        { kCTParagraphStyleSpecifierTailIndent, sizeof(tailIndent), &tailIndent },        { kCTParagraphStyleSpecifierLineBreakMode, sizeof(lineBreakMode), &lineBreakMode },        { kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(lineHeightMutiple), &lineHeightMutiple },        { kCTParagraphStyleSpecifierLineSpacing, sizeof(lineHeightMutiple), &lineHeightMutiple },        { kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(paragraphBeforeSpace), &paragraphBeforeSpace },        { kCTParagraphStyleSpecifierParagraphSpacing, sizeof(paragraphAfterSpace), &paragraphAfterSpace },        { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(writeDirect), &writeDirect },    };        CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(paragraphSettings, kNumberOfSettings);        /**     *   字型屬性     */    CGFloat fontSize = config.fontSize;    //use the postName after iOS10//    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)config.fontName, fontSize, NULL);    CTFontRef fontRef = CTFontCreateWithName(NULL, fontSize, NULL);//    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"TimesNewRomanPSMT", fontSize, NULL);       UIColor * textColor = config.textColor;        //設定字型間距    long number = config.wordSpace;    CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number);            NSMutableDictionary * dict = [NSMutableDictionary dictionary];    dict[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor;    dict[(id)kCTFontAttributeName] = (__bridge id)fontRef;    dict[(id)kCTKernAttributeName] = (__bridge id)num;    dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef;        CFRelease(num);    CFRelease(theParagraphRef);    CFRelease(fontRef);    return dict;}

上述過程為先根據上面提供的段落屬性值產生段落屬性,然後產生字型、字型間距及字型顏色等屬性,然後依次將他們存入字典中。

需要注意的地方是 CTParagraphStyleSetting 為C語言的數組,需在建立時指定數組元素個數。

建立的CoreFoundation庫中的對象需要手動釋放(大部分到create方法產生的對象)

另外在系統升級到iOS10以後,在調節字型大小重新排版時,變得很慢,用Instrument查了一下,發現 

CTFontRef fontRef = CTFontCreateWithName((CFStringRef)config.fontName, fontSize, NULL);

這句代碼執行時間很長,尋找資料發現是字型造成的,iOS10需要用相應的POST NAME。

 

2、由屬性字串得到CTFramesetter

// 建立 CTFramesetterRef 執行個體    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mabStr);

 

3、由CTFramesetter和繪製地區得到CTFrame

這一步的關鍵是要得到繪製的地區:

// 獲得要繪製的地區的高度    CGSize restrictSize = CGSizeMake(viewWidth, CGFLOAT_MAX);    CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), nil, restrictSize, nil);    CGFloat textHeight = coreTextSize.height;

然後產生CTFrame:

//產生繪製的地區+ (CTFrameRef)createFrameWithFramesetter:(CTFramesetterRef)framesetter frameWidth:(CGFloat)frameWidth stringRange:(CFRange)stringRange orginY:(CGFloat)originY height:(CGFloat)frameHeight{    CGMutablePathRef path = CGPathCreateMutable();    CGPathAddRect(path, NULL, CGRectMake(0, originY, frameWidth, frameHeight)); //此時path的位置值都是coretext座標系下的值        CTFrameRef frame = CTFramesetterCreateFrame(framesetter, stringRange, path, NULL);        CFRelease(frame);    CFRelease(path);        return frame;}

這裡需要注意的地方就是代碼中注釋的地方,在排版過程中使用的座標都是在coretext座標系下的,即原點在螢幕左下角。

 

4、將CTFrame渲染到視圖的上下文中

這一步是要在視圖類的drawRect方法中將上步得到的CTFrame繪製出來:

- (void)drawRect:(CGRect)rect{    [super drawRect:rect];        //將座標系轉換為coretext下的座標系    CGContextRef context = UIGraphicsGetCurrentContext();    CGContextSetTextMatrix(context, CGAffineTransformIdentity);    CGContextTranslateCTM(context, 0, self.bounds.size.height);    CGContextScaleCTM(context, 1.0, -1.0);        if (ctFrame != nil)     {        CTFrameDraw(ctFrame, context);      }}

這一步的關鍵是座標系的轉換,因為ctFrame中包含的繪製地區是在coretext座標系下,所以在繪製時應先將座標系轉換為coretext座標系再繪製,才能保證繪製位置正確。

如果渲染時需要精確到行或字型可用CTLine與CTRun,這會在後面介紹。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.