iOS中CoreText框架探究

閑來無事想著自己搞個富文本的工具庫,不至于每次遇見這些東西就用別人的第三方。自己研究研究也有助于自己對這方面的理解。通過查找了相關的調研發現CoreText是一個好的框架,我們系統的UILabel等控件就是基于此框架封裝的。由此我也打算搞搞看

一、CoreText框架基礎

image.png

從此架構圖可以看出,CoreText是我們平時使用的UILabel、UITextField更底層的框架。它是基于Core Graphics的,所以性能上更加的快速。

UIWebView也是我們處理復雜的文字排本的方案,那CoreText和基于UIWebView相比有哪些異同呢?

優勢:
  • CoreText占用的內存更少,渲染的速度更快,UIWebView占用的內存多,渲染速度更慢。
  • CoreText 在渲染界面前就可以精確地獲得顯示內容的高度(只要有了CTFrame就可以),而UIWebView只有渲染出內容后,才能獲得內容的高度(通過js代碼獲取)
  • CoreText的CTFrame可以在后臺線程渲染,UIWebView的內容只能在主線程(UI線程)渲染。
  • 基于CoreText可以做更好的原生交互效果,交互效果可以更細膩。而UIWebView的交互效果都是用JS來實現的,在交互效果上會有一些卡頓情況存在。例如在UIWebView下,一個簡單的按鈕按下操作,都無法做出原生按鈕的即時和細膩效果。
劣勢:
  • CoreText渲染出來的內容不能像UIWebView那樣方便的支持內容的復制。
  • 基于CoreText來排版需要自己處理很多復雜邏輯,例如需要自己處理圖片與文字混排相關的邏輯,也需要自己實現鏈接點擊操作的支持。

業界很多應用都采用了基于CoreText技術的排版方案,例如:新浪微博客戶端、多看閱讀客戶端。

常用類、屬性
  • CTFrameRef
  • CTFramesetterRef
  • CTLineRef
  • CTRunRef
  • CTTypesetterRef
  • CTGlyhInfoRef (NSGlyphInfo)
  • CTParagraphStyleRef (NSParapraphStyle)
  • CTFontRef (UIFont)
  • CFArrayRef (NSArray)
字體結構:
image.png
CTFrame、CTRun、CTLine
image.png
  • CTFrame可以想象成一個畫布,畫布的大小范圍由CGPath決定
  • CTFrame由很多CTLine組成,CTLine表示為一行CTLine由多個CTRun組成,CTRun相當于一行中的多個塊(格式為一致的字為一個塊)
    但是CTRun不需要你自己創建,由NSAttributedString的屬性決定,系統自動生成。每個CTRun對應不同屬性。
  • CTFramesetter是一個工廠,創建CTFrame,一個界面上可以有多個CTFrame
  • CTFrame就是一個基本畫布,然后一行一行繪制。CoreText會自動根據傳入的NSAttributedString屬性創建CTRun,包括字體樣式,顏色,間距等
流程
image.png
    1. 創建AttributedString,定義樣式
  • 2、通過CFAttributedStringRef生成CTFramesetter
    1. 通過CTFramesetter得到CTFrame
  • 4.繪制(CTFrameDraw)
  • 5.如果有圖片存在,先在AttributedString對應位置添加占位字符(空字符串),因為CoreText是不支持圖片的
    1. 通過回調函數確定圖片的寬高(CTRunDelegateCallbacks)
    1. 遍歷到對應CTRun上、獲取對應CGRect、繪制圖片(CGContextDrawImage)
  • 8.如果想做點擊對應的圖片的回調可以記錄圖片的位置,同時確定點擊的位置是不是在圖片的位置上做處理
    1. 如果想做鏈接點擊處理,則需要確定此鏈接上的所有CTRun的位置,然后判斷點擊的點是不是在此位置上做處理

二、基本的文本樣式的具體代碼

CoreText是需要我們自己處理繪制,不像UILabel等最上層的控件,我們必須在drawRect中繪制,為了更好的使用,我們稍微封裝一哈,自定義一個UIView

我們在使用最上層的控件時,坐標系的原點在左上角,而底層的CoreGraphics的坐標原點則在左下角,(垮平臺圖形繪制框架OpenGL的坐標系就是如此的)

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    //step 1:獲取當前畫布的上下文,用于后續將內容繪制在畫布上
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //step 2: 創建繪制區域,CoreText本身支持各種文字排版的區域,我們這里使用整個UIView作為排版區域
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    

    //step 3:  創建繪制的文字,為NSAttributedString類型
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"xXHhofiyYI這是一段中文,前面是大小寫"];


    
    //step 4:  通過NSAttributedString轉換為CTFramesetterRef,然后通過CTFramesetterRef創建CTFrameRef
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attributedString length]), path, NULL);
    
    //step 5:  開始繪制文字
    CTFrameDraw(frame,context);
    
    //step 6:  釋放對象
    CFRelease(frame);
    CFRelease(path);
    CFRelease(framesetter);
    //使用Create函數建立的對象引用,必須要使用CFRelease掉。
}

此時得到的效果如下:是翻轉的


image.png

結果分析:發現文案是反的。原因就是因為coreText的坐標系和UIKit的坐標系不一樣的。

image.png

因此我們需要將坐標系進行翻轉

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

此時就的得到了一個正常的文字顯示。
以上的繪制方式都是基于CTFrame繪制的,還可以按照CTLine和CTRun繪制:

按CTLine繪制
    // 通過CTLine
    // 1.獲得CTLine數組
    CFArrayRef lines = CTFrameGetLines(frame);
    // 2.獲得行數
    CFIndex indexCount = CFArrayGetCount(lines);
    // 3.獲得每一行的origin, CoreText的origin是在字形的baseLine(基準線)處
    CGPoint origins[indexCount];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
    
    // 4.遍歷每一行進行繪制
  for (int i = 0; i < indexCount; i++) {


        CTLineRef line = CFArrayGetValueAtIndex(lines, i);

        CTLineDraw(line, context);
    }
   // 繪制的文字內容是:Worl按季度交發十大減肥;阿技術點發覺啊;啥的積分;阿斯加德發;安靜是的;發jakdfads;fjas;lsd f安靜的首付款撒;時間點發;安靜都是;發覺啊;是的發;啊打發; 

結果如下:


image.png

從UIView的底部開始繪制的,且沒有繪制安全,還是從基準線分割了文字

按CTRun繪制

用下面函數替換CTLineDraw(line, context)這一句就可以了,

for (int i = 0; i < indexCount; i++) {


        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        
        CFArrayRef runs = CTLineGetGlyphRuns(line);
        CFIndex runCount = CFArrayGetCount(runs);
        for (int j = 0; j < runCount; j++) {
            
            CTRunRef run = CFArrayGetValueAtIndex(runs, j);
            
            CTRunDraw(run, context, CFRangeMake(0, 0));
        }
    }

結果如下:


image.png

文字疊加了

三、圖文混排

CoreText本身是不提供UIImage的繪制的,所以UIImage肯定只能通過Core Graphics繪制,但是繪制時必須要知道繪制單元的長寬,慶幸的是CoreText繪制的最小單元CTRun提供了CTRunDelegate,也就是當設置了kCTRunDelegateAttributedName之后,CTRun的繪制時所需的參考(長寬等)將可以從委托中獲取,我們即可通過此方法實現圖片的繪。在需要繪制圖片的位置,提前預留空白位。

CTRun有幾個委托用以實現CTRun的幾個參數的獲取
以下是CTRunDelegateCallbacks的幾個委托代理

typedef struct
{
    CFIndex                         version;
    CTRunDelegateDeallocateCallback dealloc;
    CTRunDelegateGetAscentCallback  getAscent;
    CTRunDelegateGetDescentCallback getDescent;
    CTRunDelegateGetWidthCallback   getWidth;
} CTRunDelegateCallbacks;

以下是基本繪制

/// 固定圖文混排
- (void) drawTextAndImg {
    
    // CoreText為了排版,需要將顯示的文本內容,位置,字體,字形傳遞給Quartz
    
    // 步驟1 獲取當前畫布的上下文,用于后續將內容繪制在畫布上
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    /*
     步驟2
     將坐標系上下翻轉。對于底層的繪制引擎來說,屏幕的左下角是(0,0)坐標。
     而對于上層的UIKit來說,左上角是(0,0)坐標。所以我們為了之后的坐標系描述按UIKit來做,先在這里做一個坐標系的上下翻轉操作。
     翻轉之后,底層和上層的(0,0)坐標就是重合了
     
     */
    CGContextSetTextMatrix(context, CGAffineTransformIdentity); // 設置矩陣(紋理)
    CGContextTranslateCTM(context, 0, self.bounds.size.height); // 內容翻轉
    CGContextScaleCTM(context, 1.0, -1.0); //
    
    /*
     步驟3
     
     創建繪制的區域,CoreText本身支持各種文字排版的區域,我們這里簡單的將UIView的整個界面作為排版的區域。
     
     為了加深理解,我們可以替換區域為下面的橢圓區域
     */
    CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, self.bounds);
//    CGPathAddEllipseInRect(path, NULL, self.bounds); // 橢圓區域
    
    
    /**
     含有圖片的  步驟1
     */
    CTRunDelegateCallbacks imageCallBacks;
    imageCallBacks.version = kCTRunDelegateCurrentVersion;
    imageCallBacks.dealloc = ImgRunDelegateDeallocCallback;
    imageCallBacks.getAscent = ImgRunDelegateGetAscentCallback;
    imageCallBacks.getDescent = ImgRunDelegateGetDescentCallback;
    imageCallBacks.getWidth = ImgRunDelegateGetWidthCallback;
    
    NSString *imgName = @"coretext-image-1.jpg";
    CTRunDelegateRef imgRunDelegate = CTRunDelegateCreate(&imageCallBacks, (__bridge void * _Nullable)(imgName)); // 我們也可以傳入其他參數
    NSMutableAttributedString *imgAttributedStr = [[NSMutableAttributedString alloc] initWithString:@" "];
    [imgAttributedStr addAttribute:(NSString *)kCTRunDelegateAttributeName value:(__bridge  id)imgRunDelegate range:NSMakeRange(0, 1)];
    
    
    


    
    // 步驟4
    NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:@" Worl按季度交發十大減肥;阿技術點發覺啊;啥的積分;阿斯加德發;安靜是的;發jakdfads;fjas;lsd f安靜的首付款撒;時間點發;安靜都是;發覺啊;是的發;啊打發; "];
    [attString addAttribute:(NSString *)kCTBackgroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 10)];
    [attString addAttribute:(NSString *)kCTFontAttributeName value:[UIFont systemFontOfSize:18] range:NSMakeRange(0, 10)];
    
    
    /**
     含有圖片的  步驟2
     */
#define kImgName @"imgName"
    // 圖片占位符添加
    [imgAttributedStr addAttribute:kImgName value:imgName range:NSMakeRange(0, 1)];
    [attString insertAttributedString:imgAttributedStr atIndex:30];
    
    
    
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL);
    
    
    
    

    
    
    // 步驟5  開始繪制
    CTFrameDraw(frame, context);
    
    /**
     含有圖片的  步驟3 繪制圖片
     */
    // 通過CTLine
    // 1.獲得CTLine數組
    CFArrayRef lines = CTFrameGetLines(frame);
    // 2.獲得行數
    CFIndex indexCount = CFArrayGetCount(lines);
    // 3.獲得每一行的origin, CoreText的origin是在字形的baseLine(基準線)處
    CGPoint lineOrigins[indexCount];
    
    // 獲得第幾行的起始點
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);

    
    for (int i = 0; i < indexCount; i++) {


        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        CGFloat lineAscent; // 上緣線
        CGFloat lineDescent; // 下緣線
        CGFloat lineLeading; // 行間距
        // 獲取此行的字形參數
        CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
        
        
        // 獲取此行中每個CTRun
        CFArrayRef runs = CTLineGetGlyphRuns(line);
        CFIndex runCount = CFArrayGetCount(runs);
        for (int j = 0; j < runCount; j++) {
            
            CGFloat runAscent; // 此CTRun上緣線
            CGFloat runDescent; // 此CTRun下緣線
            CGFloat runLeading; // CTRun間距
            CGPoint lineOrigin = lineOrigins[i]; // 此行起點
            
        
            
            // 獲取此CTRun
            CTRunRef run = CFArrayGetValueAtIndex(runs, j);
            
            // 獲取該run上的屬性特征
            NSDictionary *runAttributeds = (NSDictionary *)CTRunGetAttributes(run);
            
            CGRect runRect;
            // 獲取此CTRun的上緣線、下緣線,并由此獲取CTRun和寬
            runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &runAscent, &runDescent, &runLeading);
            
            // CTRun的X坐標
            CGFloat runOrgX = lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
#warning ---此處的y結果沒看懂,高也沒看懂---
            // 此處的y結果沒看懂  高也沒看懂
            runRect = CGRectMake(runOrgX, lineOrigin.y - runDescent, runRect.size.width, runAscent + runDescent);
            
            // 通過run的屬性特征獲得圖片名稱的字符串
            NSString *imgName = [runAttributeds objectForKey:kImgName];
            NSLog(@"圖片名稱===%@",imgName);
            if (imgName != nil) {
                
                UIImage *image = [UIImage imageNamed:imgName];
                
                if (image) {
                    
#warning ---此處的坐標計算也沒看懂---
                    CGRect imageRect;
                    imageRect.size = CGSizeMake(40, 20);
                    imageRect.origin.x = runRect.origin.x + lineOrigin.x;
                    imageRect.origin.y = lineOrigin.y;
                    
                    CGContextDrawImage(context, imageRect, image.CGImage);
                }
            }
            
            
        }
    }
    
    
    
    
    
    
    

    // 步驟6  釋放內存
    CFRelease(imgRunDelegate);
    CFRelease(frame);
    CFRelease(path);
    CFRelease(framesetter);
}
#pragma mark ---代理函數CTRunDelegateCallbacks---
void ImgRunDelegateDeallocCallback(void *refCon) {
    
}

/// 通過此函數設置圖片處上部高
CGFloat ImgRunDelegateGetAscentCallback(void *refCon) {
    
    NSString *imageName = (__bridge  NSString *)refCon;
//    return [UIImage imageNamed:imageName].size.height;
    return 40;
}

/// 通過此函數設置圖片處下部高
CGFloat ImgRunDelegateGetDescentCallback(void *refCon) {
    
    return 0;
}

/// 通過此函數設置圖片位置寬度
CGFloat ImgRunDelegateGetWidthCallback(void *refCon) {
    
    NSString *imageName = (__bridge  NSString *)refCon;
//    return [UIImage imageNamed:imageName].size.width;
    return 40;
}

結果如下:


image.png

基于以上這個原型,我們可以封裝一個比較完整的富文本控件了,比如定義HTML協議或者JSON,然后在內部進行解析,然后根據類型與相應的屬性進行繪制。

四、圖片點擊事件

CoreText就是將內容繪制到畫布上,自然沒有事件處理,我們要實現圖片與鏈接的點擊效果就需要使用觸摸事件了。當點擊的位置在圖片的Rect中,那我們做相應的操作即可,所以基本步驟如下:

  • 記錄所有圖片所在畫布中作為一個CTRun的位置
  • 獲取每個圖片所在畫布中所占的Rect矩形區域
  • 當點擊事件發生時,判斷點擊的點是否在某個需要處理的圖片Rect內。

這里為了演示的簡單,我們直接在drawRect中記錄圖片的相應坐標,但是一般我們會在CTDisplayView渲染之前對數據進行相應的處理,比如處理傳入的樣式數據、記錄圖片與鏈接等信息。

用于記錄圖片信息類

@interface CTImageData : NSObject
@property (nonatomic,strong) NSString *imgHolder;
@property (nonatomic,strong) NSURL *imgPath;
@property (nonatomic) NSInteger idx;
@property (nonatomic) CGRect imageRect;
@end

// 記錄圖片信息

//以下操作僅僅是演示示例,實戰時請在渲染之前處理數據,做到最佳實踐。  
if(!_imageDataArray){
    _imageDataArray = [[NSMutableArray alloc]init];
}
BOOL imgExist = NO;
for (CTImageData *ctImageData in _imageDataArray) {
    if (ctImageData.idx == idx) {
        imgExist = YES;
        break;
    }
}
if(!imgExist){
    CTImageData *ctImageData = [[CTImageData alloc]init];
    ctImageData.imgHolder = imgName;
    ctImageData.imageRect = imageRect;
    ctImageData.idx = idx;
    [_imageDataArray addObject:ctImageData];
}
- (void)setupEvents{
    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(userTapGestureDetected:)];
    
    [self addGestureRecognizer:tapRecognizer];
    
    self.userInteractionEnabled = YES;
}

- (void)userTapGestureDetected:(UIGestureRecognizer *)recognizer{
    CGPoint point = [recognizer locationInView:self];
    //先判斷是否是點擊的圖片Rect
    for(CTImageData *imageData in _imageDataArray){
        CGRect imageRect = imageData.imageRect;
        CGFloat imageOriginY = self.bounds.size.height - imageRect.origin.y - imageRect.size.height;
        CGRect rect = CGRectMake(imageRect.origin.x,imageOriginY, imageRect.size.width, imageRect.size.height);
        if(CGRectContainsPoint(rect, point)){
            NSLog(@"tap image handle");
            return;
        }
    }
    
    //再判斷鏈接
}

五、鏈接點擊事件

記錄鏈接信息類

@interface CTLinkData : NSObject
@property (nonatomic ,strong) NSString *text;
@property (nonatomic ,strong) NSString *url;
@property (nonatomic ,assign) NSRange range;
@end

記錄鏈接信息

if(!_linkDataArray){
    _linkDataArray = [[NSMutableArray alloc]init];
}
CTLinkData *ctLinkData = [[CTLinkData alloc]init];
ctLinkData.text = [attributedString.string substringWithRange:linkRange];
ctLinkData.url = @"http://www.baidu.com";
ctLinkData.range = linkRange;
[_linkDataArray addObject:ctLinkData];

處理鏈接事件

if(!_linkDataArray){
    _linkDataArray = [[NSMutableArray alloc]init];
}
CTLinkData *ctLinkData = [[CTLinkData alloc]init];
ctLinkData.text = [attributedString.string substringWithRange:linkRange];
ctLinkData.url = @"http://www.baidu.com";
ctLinkData.range = linkRange;
[_linkDataArray addObject:ctLinkData];

根據點擊點獲取字符串偏移

- (CFIndex)touchPointOffset:(CGPoint)point{
    //獲取所有行
    CFArrayRef lines = CTFrameGetLines(_ctFrame);
    
    if(lines == nil){
        return -1;
    }
    CFIndex count = CFArrayGetCount(lines);
    
    //獲取每行起點
    CGPoint origins[count];
    CTFrameGetLineOrigins(_ctFrame, CFRangeMake(0, 0), origins);
    
    
    //Flip
    CGAffineTransform transform =  CGAffineTransformMakeTranslation(0, self.bounds.size.height);
    transform = CGAffineTransformScale(transform, 1.f, -1.f);
    
    CFIndex idx = -1;
    for (int i = 0; i< count; i++) {
        CGPoint lineOrigin = origins[i];
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        
        //獲取每一行Rect
        CGFloat ascent = 0.0f;
        CGFloat descent = 0.0f;
        CGFloat leading = 0.0f;
        CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
        CGRect lineRect = CGRectMake(lineOrigin.x, lineOrigin.y - descent, width, ascent + descent);
        
        lineRect = CGRectApplyAffineTransform(lineRect, transform);
        
        if(CGRectContainsPoint(lineRect,point)){
            //將point相對于view的坐標轉換為相對于該行的坐標
            CGPoint linePoint = CGPointMake(point.x-lineRect.origin.x, point.y-lineRect.origin.y);
            //根據當前行的坐標獲取相對整個CoreText串的偏移
            idx = CTLineGetStringIndexForPosition(line, linePoint);
        }
    }
    return idx;
}

下面是我寫的一個demo,封裝好的圖文混排的,可以看看,實戰的話稍微修改修改就好,數據是通過json傳入的。## demo鏈接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • CoreText簡介 處理文字和字體的底層技術。它直接和Core Graphics打交道,是iOS和OSX底層的告...
    taobingzhi閱讀 1,082評論 0 3
  • 支持圖文混排的排版引擎 改造模版文件 下面我們來進一步改造,讓排版引擎支持對于圖片的排版。在上一小節中,我們在設置...
    sunney0閱讀 793評論 0 0
  • Core Text is an advanced, low-level technology for laying...
    forping閱讀 1,849評論 0 3
  • CoreText 是用于處理文字和字體的底層技術。它直接和 Core Graphics(又被稱為 Quartz)打...
    SpursGo閱讀 1,785評論 0 2
  • 今天感恩節哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉變要...
    迷月閃星情閱讀 10,597評論 0 11