CoreText(一)

CoreText是apple提供的處理文字和字體的底層技術(shù)。他直接和Quartz打交道,Quartz能夠處理OSX和iOS中的圖形顯示极颓。
Quartz能夠處理字體、字形躯肌,將文字渲染到界面上,它是基礎(chǔ)庫中唯一能夠處理字形的模塊校仑。因此CoreText為了排版卦方,需要將文本的內(nèi)容、位置、字體近刘、字形直接傳遞給Quartz徽惋。相比于蘋果提供的UI框架中的組件踢京,CoreText直接和Quartz來進(jìn)行交互,具有較高的排版效果。

1.蘋果的基礎(chǔ)框架

coretext_arch.png

從上圖中,可以看出蘋果的CoreText處于Core Graphics的上一層厦取,處于Text KitWebKit的下一層更鲁,UI組件和UIWebview處于更高的層,且都是基于CoreText的上層,可以斷定CoreText應(yīng)該會(huì)有更高的定制化以及更高的效率。

CoreText的框架
core_text底層框架.png
  • CTFrame可以理解過畫布,畫布的大小有CGPath決定
  • CTFrame由很多CTLine組成, CTLine表示為一行
  • CTLine由多個(gè)CTRun組成, CTRun相當(dāng)于一行中的多個(gè)塊, 但是CTRun不需要你自己創(chuàng)建, 由NSAttributedString的屬性決定, 系統(tǒng)自動(dòng)生成指蚜。每個(gè)CTRun對應(yīng)不同屬性
  • CTFramesetter是一個(gè)工廠, 創(chuàng)建CTFrame, 一個(gè)界面上可以有多個(gè)CTFrame

2.建立一個(gè)輸出Hello World的工程

項(xiàng)目很簡單,我們不說廢話,直接寫一下核心的代碼。
創(chuàng)建一個(gè)功能棺聊,新建一個(gè)CTDisplayView.swift重寫一下draw(_ rect: CGRect),然后把這個(gè)View貼出來就可以了祟同。
我們來看一下draw(_ rect: CGRect)里面的代碼

class CTDisplayView: UIView {

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        // 1.獲取上下文
        let context = UIGraphicsGetCurrentContext()
        
        // 2.轉(zhuǎn)換坐標(biāo)
        context?.textMatrix = .identity
        context?.translateBy(x: 0, y: self.bounds.size.height)
        context?.scaleBy(x: 1.0, y: -1.0)
        
        // 3獲取路徑
        let path = CGMutablePath()
        path.addRect(self.bounds)
        
        // 4. 文本
        let str = "Hello world"
        let mutableAttrStr = NSMutableAttributedString(string: str)
        //設(shè)置行間距
        let style = NSMutableParagraphStyle()
        style.lineSpacing = 10
        //5. 設(shè)置CTFramesetter砖顷,創(chuàng)建CTFrame
        let frameSetter = CTFramesetterCreateWithAttributedString(mutableAttrStr)
        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, mutableAttrStr.length), path, nil)
      //6. 畫出來
        CTFrameDraw(frame, context!)
    }
    
}

先看一下run起來的輸出再分析代碼


HelloWorld.jpeg

代碼其實(shí)很簡單房轿,首先獲取上下文,用于后續(xù)將內(nèi)容繪制在畫布上掩幢。然后將坐標(biāo)系上下翻轉(zhuǎn)世曾,這是因?yàn)閷τ诘讓拥睦L制引擎來說,屏幕的左下角是(0, 0)坐標(biāo)。而對于上層的 UIKit 來說优俘,左上角是 (0, 0) 坐標(biāo)叶雹。所以我們?yōu)榱酥蟮淖鴺?biāo)系描述按 UIKit 來做谦炒,所以先在這里做一個(gè)坐標(biāo)系的上下翻轉(zhuǎn)操作。翻轉(zhuǎn)之后风喇,底層和上層的 (0, 0) 坐標(biāo)就是重合的了宁改。
我們現(xiàn)在翻轉(zhuǎn)坐標(biāo)系的代碼注釋掉,會(huì)看到這樣的效果魂莫。


翻轉(zhuǎn)HelloWorld.jpeg
  • 繪制區(qū)域还蹲,CoreText支持各種文字排版區(qū)域,我們這里簡單的講view的邊界作為了排版區(qū)域耙考,可以看下面一個(gè)例子秽誊,看一下CoreText是如何支持文字排版區(qū)域的
// 3獲取路徑
let path = CGMutablePath()
path.addEllipse(in: self.bounds)
// 4. 文本
let str = "Hello World! 創(chuàng)建繪制的區(qū)域,CoreText 本身支持各種文字排版的區(qū)域琳骡,我們這里簡單地將 UIView 的整個(gè)界面作為排版的區(qū)域锅论。 為了加深理解,建議讀者將該步驟的代碼替換成如下代碼楣号, 測試設(shè)置不同的繪制區(qū)域帶來的界面變化最易。"

效果圖如下:


橢圓區(qū)域.jpeg

3.做一個(gè)簡單的排版引擎

從上面的demo中可以看出CoreText具有排版的能力怒坯,我們簡單的把所有的代碼都放在了draw(_ rect: CGRect)中,這樣顯然是不合理的藻懒。下面我們嘗試做幾個(gè)模塊剔猿,把不同的功能都放到各自不同的類里面。
對于一個(gè)復(fù)雜的排版引擎來說嬉荆,可以將其功能拆成以下幾個(gè)類來完成:
一個(gè)顯示用的類归敬,僅負(fù)責(zé)顯示內(nèi)容,不負(fù)責(zé)排版
一個(gè)模型類鄙早,用于承載顯示所需要的所有數(shù)據(jù)
一個(gè)排版類汪茧,用于實(shí)現(xiàn)文字內(nèi)容的排版
一個(gè)配置類,用于實(shí)現(xiàn)一些排版時(shí)的可配置項(xiàng)
按照以上原則限番,我們將CTDisplayView中的部分內(nèi)容拆開舱污,由 4 個(gè)類構(gòu)成:
CTFrameParserConfig類,用于配置繪制的參數(shù)弥虐,例如:文字顏色扩灯,大小,行間距等霜瘪。
CTFrameParser類珠插,用于生成最后繪制界面需要的CTFrameRef實(shí)例。
CoreTextData類颖对,用于保存由CTFrameParser類生成的CTFrameRef實(shí)例以及CTFrameRef實(shí)際繪制需要的高度捻撑。
CTDisplayView類,持有CoreTextData類的實(shí)例惜互,負(fù)責(zé)將CTFrameRef繪制到界面上布讹。
下面我們把這些代碼貼出來讀一下:

class CTFrameParserConfig: NSObject {
    
    var width: CGFloat
    var fontSize: CGFloat
    var lineSpace: CGFloat
    var textColor: UIColor
    
    override init() {
        self.width = 200.0
        self.fontSize = 16.0
        self.lineSpace = 8.0
        self.textColor = UIColor.init(colorLiteralRed: 108, green: 108, blue: 108, alpha: 1)
    }
    
}
class CTFrameParser: NSObject {
    
    class func parseContent(content: NSString, config: CTFrameParserConfig) -> CoreTextData {
        let attributes = self.attributesWithConfig(config: config)
        let contentString = NSAttributedString(string: content as String, attributes: attributes as! [String : Any])
        
        // 創(chuàng)建 CTFramesetterRef 實(shí)例
        let frameSetter = CTFramesetterCreateWithAttributedString(contentString)
        
        // 獲得要繪制的區(qū)域的高度
        let restrictSize = CGSize(width: config.width, height: CGFloat.greatestFiniteMagnitude)
        let coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil)
        let textHeight = coreTextSize.height
        
        // 生成 CTFrameRef 實(shí)例
        let frame = self.createFrameWithFramesetter(framesetter: frameSetter, config: config, height: textHeight)
        
        // 將生成好的 CTFrameRef 實(shí)例和計(jì)算好的繪制高度保存到 CoreTextData 實(shí)例中琳拭,最后返回 CoreTextData 實(shí)例
        let data = CoreTextData()
        data.ctFrame = frame
        data.height = textHeight
        return data
    }
    
    class func attributesWithConfig(config: CTFrameParserConfig) -> NSDictionary {
        let fontSize = config.fontSize
        let fontRef = CTFontCreateWithName("ArialMT" as CFString?, fontSize, nil)
        var lineSpace = config.lineSpace
        
        var theSettings: [CTParagraphStyleSetting] =  [CTParagraphStyleSetting]()
        
        let theSettingLine = CTParagraphStyleSetting(spec: .lineSpacingAdjustment, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace)
        theSettings.append(theSettingLine)
        let theSettingMax = CTParagraphStyleSetting(spec: .maximumLineSpacing, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace)
        theSettings.append(theSettingMax)
        let theSettingMin = CTParagraphStyleSetting(spec: .minimumLineSpacing, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace)
        theSettings.append(theSettingMin)
        
        let theParagraphRef = CTParagraphStyleCreate(theSettings, 3)
        
        let textColor = config.textColor
        
        let dict = NSMutableDictionary()
        dict[kCTForegroundColorAttributeName] = textColor.cgColor
        dict[kCTFontAttributeName] = fontRef
        dict[kCTParagraphStyleAttributeName] = theParagraphRef
        
        return dict
    }
    
    class func createFrameWithFramesetter(framesetter: CTFramesetter, config: CTFrameParserConfig, height: CGFloat) -> CTFrame {
        let path = CGMutablePath()
        path.addRect(CGRect(x: 0, y: 0, width: config.width, height: height))
        let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)
        return frame
    }
}
class CoreTextData: NSObject {
    
    var ctFrame: CTFrame?
    var height: CGFloat?
    
}
class CTDisplayView: UIView {
    
    var data: CoreTextData?

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        // 1.獲取上下文
        let context = UIGraphicsGetCurrentContext()
        
        // 2.轉(zhuǎn)換坐標(biāo)
        context?.textMatrix = .identity
        context?.translateBy(x: 0, y: self.bounds.size.height)
        context?.scaleBy(x: 1.0, y: -1.0)
        
        // 根據(jù)數(shù)據(jù)繪制
        if((self.data) != nil) {
            CTFrameDraw((data?.ctFrame)!, context!)
        }
    }
    
}

在使用CTDisplayView的viewcontroller里面我只需要這樣做就可以了:

class ViewController: UIViewController {

    @IBOutlet weak var ctView: CTDisplayView!
    override func viewDidLoad() {
        super.viewDidLoad()
        let config = CTFrameParserConfig()
        config.textColor = UIColor.red
        config.width = self.ctView.frame.size.width
        
        let data = CTFrameParser.parseContent(content: " 按照以上原則训堆,我們將CTDisplayView中的部分內(nèi)容拆開。", config: config)
        self.ctView.data = data
      //  self.ctView.frame = CGRect(self.ctView.frame.origin.x, self.ctView.frame.origin.y, self.ctView.frame.size.width, data.height)
        self.ctView.setHeight(data.height!)
        self.ctView.backgroundColor = UIColor.yellow
    }

}

運(yùn)行起來看一下效果:


WechatIMG8.jpeg

如果我們希望給一段話的不同的區(qū)域設(shè)置不同的顏色白嘁,其實(shí)只要設(shè)置attributedString的屬性就可以實(shí)現(xiàn)了:

class ViewController: UIViewController {

    @IBOutlet weak var ctView: CTDisplayView!
    override func viewDidLoad() {
        super.viewDidLoad()
        let config = CTFrameParserConfig()
        config.textColor = UIColor.black
        config.width = self.ctView.frame.size.width
        
        let content = " 對于上面的例子坑鱼,我們給 CTFrameParser 增加了一個(gè)將 NSString 轉(zhuǎn)  換為 CoreTextData 的方法。 但這樣的實(shí)現(xiàn)方式有很多局限性絮缅,因?yàn)檎麄€(gè)內(nèi)容雖然可以定制字體  大小鲁沥,顏色,行高等信息耕魄,但是卻不能支持定制內(nèi)容中的某一部分画恰。 例如,如果我們只想讓內(nèi)容的前三個(gè)字顯示成紅色吸奴,而其它文字顯  示成黑色允扇,那么就辦不到了缠局。\n\n 解決的辦法很簡單,我們讓`CTFrameParser`支持接受 NSAttributeString 作為參數(shù)考润,然后在 NSAttributeString 中設(shè)置好  我們想要的信息狭园。"
        let attr = CTFrameParser.attributesWithConfig(config: config)
        let attributedString = NSMutableAttributedString(string: content, attributes: attr as? [String : Any])
        attributedString.addAttributes([kCTForegroundColorAttributeName as String: UIColor.red], range: NSMakeRange(0, 8))
        
        let  data = CTFrameParser.parseAttributedContent(content: attributedString, config: config)
        self.ctView.data = data
        self.ctView.setHeight(data.height!)
        self.ctView.backgroundColor = UIColor.yellow
    }

}

在CTFrameParser這個(gè)類中需要加一個(gè)方法:

class func parseAttributedContent(content: NSAttributedString, config: CTFrameParserConfig) -> CoreTextData {
        
        let frameSetter = CTFramesetterCreateWithAttributedString(content)
        
        // 獲得要繪制的區(qū)域的高度
        let restrictSize = CGSize(width: config.width, height: CGFloat.greatestFiniteMagnitude)
        let coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil)
        let textHeight = coreTextSize.height
        
        // 生成 CTFrameRef 實(shí)例
        let frame = self.createFrameWithFramesetter(framesetter: frameSetter, config: config, height: textHeight)
        
        // 將生成好的 CTFrameRef 實(shí)例和計(jì)算好的繪制高度保存到 CoreTextData 實(shí)例中,最后返回 CoreTextData 實(shí)例
        let data = CoreTextData()
        data.ctFrame = frame
        data.height = textHeight
        return data
    }

運(yùn)行起來看一下:


WechatIMG9.jpeg

就目前來看糊治,我們現(xiàn)在做的這個(gè)view基于CoreText
具有繪制文本的能力唱矛,也具有給文本不同的區(qū)域添加文字屬性的能力,但是用起來其實(shí)是非常不方便的井辜,在真正的項(xiàng)目中绎谦,我們其實(shí)希望有一套規(guī)則,有一個(gè)排版的文件來設(shè)置要文字的字體抑胎,顏色等信息≡锘現(xiàn)在我們基于常用的json格式來做一下這件事。

[ { "color" : "blue",
    "content" : " 更進(jìn)一步地阿逃,實(shí)際工作中铭拧,我們更希望通過一個(gè)排版文件,來設(shè)置需要排版的文字的 ",
    "size" : 16,
    "type" : "txt"
  },
  { "color" : "red",
    "content" : " 內(nèi)容恃锉、顏色搀菩、字體 ",
    "size" : 22,
    "type" : "txt"
  },
  { "color" : "black",
    "content" : " 大小等信息。\n",
    "size" : 16,
    "type" : "txt"
  },
  { "color" : "default",
    "content" : " 我在開發(fā)猿題庫應(yīng)用時(shí)破托,自己定義了一個(gè)基于 UBB 的排版模版肪跋,但是實(shí)現(xiàn)該排版文件的解析器要花費(fèi)大量的篇幅,考慮到這并不是本章的重點(diǎn)土砂,所以我們以一個(gè)較簡單的排版文件來講解其思想州既。",
    "type" : "txt"
  }
]

根據(jù)這個(gè)模板,我們實(shí)現(xiàn)一套規(guī)則代碼來解析文字萝映,并且將其顯示:

class CTFrameParser: NSObject {
    
    class func parseContent(content: NSString, config: CTFrameParserConfig) -> CoreTextData {
        let attributes = self.attributesWithConfig(config: config)
        let contentString = NSAttributedString(string: content as String, attributes: attributes as? [String : Any])
        
        // 創(chuàng)建 CTFramesetterRef 實(shí)例
        let frameSetter = CTFramesetterCreateWithAttributedString(contentString)
        
        // 獲得要繪制的區(qū)域的高度
        let restrictSize = CGSize(width: config.width, height: CGFloat.greatestFiniteMagnitude)
        let coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil)
        let textHeight = coreTextSize.height
        
        // 生成 CTFrameRef 實(shí)例
        let frame = self.createFrameWithFramesetter(framesetter: frameSetter, config: config, height: textHeight)
        
        // 將生成好的 CTFrameRef 實(shí)例和計(jì)算好的繪制高度保存到 CoreTextData 實(shí)例中吴叶,最后返回 CoreTextData 實(shí)例
        let data = CoreTextData()
        data.ctFrame = frame
        data.height = textHeight
        return data
    }
    
    class func parseAttributedContent(content: NSAttributedString, config: CTFrameParserConfig) -> CoreTextData {
        
        let frameSetter = CTFramesetterCreateWithAttributedString(content)
        
        // 獲得要繪制的區(qū)域的高度
        let restrictSize = CGSize(width: config.width, height: CGFloat.greatestFiniteMagnitude)
        let coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil)
        let textHeight = coreTextSize.height
        
        // 生成 CTFrameRef 實(shí)例
        let frame = self.createFrameWithFramesetter(framesetter: frameSetter, config: config, height: textHeight)
        
        // 將生成好的 CTFrameRef 實(shí)例和計(jì)算好的繪制高度保存到 CoreTextData 實(shí)例中,最后返回 CoreTextData 實(shí)例
        let data = CoreTextData()
        data.ctFrame = frame
        data.height = textHeight
        return data
    }
    
    class func parseTemplateFile(path: String, config: CTFrameParserConfig) -> CoreTextData {
        let content = self.loadTemplateFile(path: path as NSString, config: config)
        let data = self.parseAttributedContent(content: content, config: config)
        return data
    }
    
    class func loadTemplateFile(path: NSString, config: CTFrameParserConfig) ->NSAttributedString {
        let fileContent = try? NSString(contentsOfFile: path as String, encoding: String.Encoding.utf8.rawValue)
        let data = NSData(contentsOfFile: path as! String)
        let result = NSMutableAttributedString()
        
        do {
            let array =  try JSONSerialization.jsonObject(with: data  as! Data , options: [JSONSerialization.ReadingOptions.init(rawValue: 0)]) as? NSArray
            for item in array! {
                let dict = item as! NSDictionary
                let type = dict.object(forKey: "type") as! String
                if type == "txt" {
                    let attributeString = self.parseAttributedContentFromNSDictionary(dict: dict, config: config)
                    result.append(attributeString)
                }
            }
        } catch _ as NSError {
            
        }
        return result
    }
    
    class func parseAttributedContentFromNSDictionary(dict: NSDictionary, config: CTFrameParserConfig) -> NSAttributedString {
        var attributes = self.attributesWithConfig(config: config) as! [String:Any]
        let color = self.colorFromTemplate(name: dict.object(forKey: "color") as! NSString)
        attributes[kCTForegroundColorAttributeName as String] = color.cgColor;
        
        let contet = dict.object(forKey: "content")
        let attributedString = NSAttributedString(string: contet as! String, attributes: attributes)
        return attributedString
    }
    
    class func attributesWithConfig(config: CTFrameParserConfig) -> NSDictionary {
        let fontSize = config.fontSize
        let fontRef = CTFontCreateWithName("ArialMT" as CFString?, fontSize, nil)
        var lineSpace = config.lineSpace
        
        var theSettings: [CTParagraphStyleSetting] =  [CTParagraphStyleSetting]()
        
        let theSettingLine = CTParagraphStyleSetting(spec: .lineSpacingAdjustment, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace)
        theSettings.append(theSettingLine)
        let theSettingMax = CTParagraphStyleSetting(spec: .maximumLineSpacing, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace)
        theSettings.append(theSettingMax)
        let theSettingMin = CTParagraphStyleSetting(spec: .minimumLineSpacing, valueSize: MemoryLayout<CGFloat>.size, value: &lineSpace)
        theSettings.append(theSettingMin)
        
        let theParagraphRef = CTParagraphStyleCreate(theSettings, 3)
        
        let textColor = config.textColor
        
        let dict = NSMutableDictionary()
        dict[kCTForegroundColorAttributeName] = textColor.cgColor
        dict[kCTFontAttributeName] = fontRef
        dict[kCTParagraphStyleAttributeName] = theParagraphRef
        
        return dict
    }
    
    class func colorFromTemplate(name: NSString) -> UIColor {
        if (name == "blue") {
            return UIColor.blue
        } else if (name == "red") {
            return UIColor.red
        } else {
            return UIColor.black
        }
    }
    
    // 生成 CTFrame
    class func createFrameWithFramesetter(framesetter: CTFramesetter, config: CTFrameParserConfig, height: CGFloat) -> CTFrame {
        let path = CGMutablePath()
        path.addRect(CGRect(x: 0, y: 0, width: config.width, height: height))
        let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)
        return frame
    }
    
}

在viewcontroller里面序臂,我們需要提供json文件的路徑蚌卤,但是實(shí)際的工作中這些信息可能來源于網(wǎng)絡(luò):

class ViewController: UIViewController {

    @IBOutlet weak var ctView: CTDisplayView!
    override func viewDidLoad() {
        super.viewDidLoad()
        let config = CTFrameParserConfig()
        config.textColor = UIColor.black
        config.width = self.ctView.frame.size.width
        let path = Bundle.main.path(forResource: "content", ofType: "json")! as String
        
        let  data = CTFrameParser.parseTemplateFile(path: path, config: config)
        self.ctView.data = data
        self.ctView.setHeight(data.height!)
        self.ctView.backgroundColor = UIColor.yellow
    }

}

看一下效果:


WechatIMG10.jpeg

到現(xiàn)在為止,我們的代碼實(shí)現(xiàn)了根據(jù)文件模板來顯示文字內(nèi)容奥秆,并根據(jù)模板提供的信息顯示不容的顏色逊彭。后面我們要把這個(gè)view做成可以顯示圖片,并支持圖片的點(diǎn)擊构订,支持鏈接點(diǎn)擊侮叮,支持?jǐn)?shù)據(jù)號(hào)碼識(shí)別,網(wǎng)頁地址鏈接識(shí)別這樣的一個(gè)控件悼瘾,一起期待吧~~~
參考:
http://yangchao0033.github.io/blog/2016/01/26/coretextji-chu/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末囊榜,一起剝皮案震驚了整個(gè)濱河市谷异,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锦聊,老刑警劉巖歹嘹,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異孔庭,居然都是意外死亡尺上,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門圆到,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怎抛,“玉大人,你說我怎么就攤上這事芽淡÷砭” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵挣菲,是天一觀的道長富稻。 經(jīng)常有香客問我,道長白胀,這世上最難降的妖魔是什么椭赋? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮或杠,結(jié)果婚禮上哪怔,老公的妹妹穿的比我還像新娘。我一直安慰自己向抢,他們只是感情好认境,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挟鸠,像睡著了一般叉信。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兄猩,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天茉盏,我揣著相機(jī)與錄音鉴未,去河邊找鬼枢冤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铜秆,可吹牛的內(nèi)容都是我干的淹真。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼连茧,長吁一口氣:“原來是場噩夢啊……” “哼核蘸!你這毒婦竟也來了巍糯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤客扎,失蹤者是張志新(化名)和其女友劉穎祟峦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徙鱼,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宅楞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袱吆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厌衙。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绞绒,靈堂內(nèi)的尸體忽然破棺而出婶希,到底是詐尸還是另有隱情,我是刑警寧澤蓬衡,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布喻杈,位于F島的核電站,受9級(jí)特大地震影響狰晚,放射性物質(zhì)發(fā)生泄漏奕塑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一家肯、第九天 我趴在偏房一處隱蔽的房頂上張望龄砰。 院中可真熱鬧,春花似錦讨衣、人聲如沸换棚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽固蚤。三九已至,卻和暖如春歹茶,著一層夾襖步出監(jiān)牢的瞬間夕玩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工惊豺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留燎孟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓尸昧,卻偏偏與公主長得像揩页,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子烹俗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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

  • 系列文章: CoreText實(shí)現(xiàn)圖文混排 CoreText實(shí)現(xiàn)圖文混排之點(diǎn)擊事件 CoreText實(shí)現(xiàn)圖文混排之文...
    老司機(jī)Wicky閱讀 40,147評論 221 432
  • iOS中的文字渲染 在iOS出現(xiàn)早期爆侣,顯示特性文本唯一可行的辦法就是使用UIWebView和利用HTML來處理定制...
    正謙閱讀 677評論 0 1
  • 字形(Character)和字符(Glyphs) 排版系統(tǒng)中文本顯示的一個(gè)重要的過程就是字符到字形的轉(zhuǎn)換萍程,字符是信...
    張周擇閱讀 565評論 0 1
  • CoreText是一個(gè)進(jìn)階的比較底層的布局文本和處理字體的技術(shù),CoreText API在OS X v10.5 和...
    smalldu閱讀 13,437評論 18 129
  • 剛好最近項(xiàng)目中有用到CoreText兔仰,最近也看了看YYText中YYLabel的源碼茫负,總結(jié)一下 CoreText實(shí)...
    軍_andy閱讀 3,666評論 0 53