Swift之CoreText排版神器(長篇高能)

圖片來源于網絡

CoreText是一個進階的比較底層的布局文本和處理字體的技術,CoreText API在OS X v10.5 和 iOS3.2時引入,在OS X 和iOS 環境下均可以使用。

不是特別復雜的需求一般情況下UILabel、UITextView都可以搞定,他們是Apple幫我們封裝好的顯示文本的控件,但是像復雜的圖文,鏈接識別并替換成 "點擊鏈接" , @someone 綁定 ,電話識別,文字大小不一 ,當然這些可以使用UIWebView,但是CoreText 技術相對于 UIWebView,有著更少的內存占用,以及可以在后臺渲染的優點,非常適合用于內容的排版工作。CoreText 提供了非常高的靈活性,但是操作起來的比較復雜,學技術不就應該找最難的攻克嗎?

來看一張框架圖

配圖

要學習CoreText 首先得了解屬性字---NSAttributedString 或者 NSMutableAttributedString

NSAttributedString 和 NSMutableAttributedString

NSAttributedString是一個帶有屬性的字符串,通過該類可以靈活地操作和呈現多種樣式的文字數據

可以事先定義好屬性 然后加到文字上

let str = "這是一段用來測試的字符串 this is a string for test"
let dic = [NSFontAttributeName:UIFont.boldSystemFontOfSize(20),
            NSForegroundColorAttributeName:UIColor.redColor()]
let attrStr = NSAttributedString(string: str, attributes: dic)
label.attributedText = attrStr

效果

配圖

可以看到我們并沒有給label設置顏色和字體,創建了一個帶兩個屬性的NSAttributedString , 沒有使用label.text而是 label.attributedText

如果只能給所有的文字設置一樣的屬性,那這個屬性字也太沒勁了。我們可以給一段文字設置不同的屬性

let mutableAttrStr = NSMutableAttributedString(string: str)
mutableAttrStr.addAttributes(dic, range: NSMakeRange(0, 2))
mutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize(13),NSUnderlineStyleAttributeName: 1 ], range: NSMakeRange(2,8))
label.attributedText = mutableAttrStr

我們可以給不同位置的文字指定不同的樣式,位置通過NSRange給出(NSRange是一個結構體,有兩個參數 一個location 一個 length ,這兩個參數可以唯一的確定一段字串)

效果

配圖

這樣我們就給前面的文字設置了兩種不同的屬性。

那么我們可以設置哪些屬性呢。

  • NSFontAttributeName 設置字體屬性,默認值:字體:Helvetica(Neue) 字號12
  • NSForegroundColorAttributeName 設置字體顏色,取值為 UIColor對象,默認值為
  • NSBackgroundColorAttributeName 設置字體所在區域背景顏色,取值為 UIColor對象,默認值為nil, 透明
  • NSLigatureAttributeName 設置連體屬性,取值為NSNumber 對象(整數),0 表示沒有連體字符,1 表示使用默認的連體字符
  • NSKernAttributeName 設定字符間距,取值為 NSNumber 對象(整數),正值間距加寬,負值間距變窄
  • NSStrikethroughStyleAttributeName 設置刪除線,取值為 NSNumber 對象(整數)
  • NSStrikethroughColorAttributeName 設置刪除線顏色,取值為 UIColor 對象,默認值為黑色
  • NSUnderlineStyleAttributeName 設置下劃線,取值為 NSNumber 對象(整數),枚舉常量 NSUnderlineStyle中的值,與刪除線類似NSUnderlineColorAttributeName 設置下劃線顏色,取值為 UIColor 對象,默認值為黑色
  • NSStrokeWidthAttributeName 設置筆畫寬度,取值為 NSNumber 對象(整數),負值填充效果,正值中空效果
  • NSStrokeColorAttributeName 填充部分顏色,不是字體顏色,取值為 UIColor 對象NSShadowAttributeName 設置陰影屬性,取值為 NSShadow 對象
  • NSTextEffectAttributeName 設置文本特殊效果,取值為 NSString 對象,目前只有圖版印刷效果可用:
  • NSBaselineOffsetAttributeName 設置基線偏移值,取值為 NSNumber (float),正值上偏,負值下偏
  • NSObliquenessAttributeName 設置字形傾斜度,取值為 NSNumber (float),正值右傾,負值左傾
  • NSExpansionAttributeName 設置文本橫向拉伸屬性,取值為 NSNumber (float),正值橫向拉伸文本,負值橫向壓縮文本
  • NSWritingDirectionAttributeName 設置文字書寫方向,從左向右書寫或者從右向左書寫
  • NSVerticalGlyphFormAttributeName 設置文字排版方向,取值為 NSNumber 對象(整數),0 表示橫排文本,1 表示豎排文本
  • NSLinkAttributeName 設置鏈接屬性,點擊后調用瀏覽器打開指定URL地址
  • NSAttachmentAttributeName 設置文本附件,取值為NSTextAttachment對象,常用于文字圖片混排
  • NSParagraphStyleAttributeName 設置文本段落排版格式,取值為 NSParagraphStyle 對象

這么多屬性使用的也記不住,使用的時候自行google用法,還是很強大的??!!
屬性字就介紹這么多,簡單富文本使用它就能搞定。下面介紹我們的主角--CoreText

CoreText

屬性字是用來給文本設置樣式,那么CoreText就是用來給文本進行排版的,可以自定每行高度,每個字符占位 等等

CoreText 是用于處理文字和字體的底層技術。它直接和 Core Graphics(又被稱為 Quartz)打交道。Quartz 是一個 2D 圖形渲染引擎。Quartz 能夠直接處理字體(font)和字形(glyphs),將文字渲染到界面上,它是基礎庫中唯一能夠處理字形的模塊。因此,CoreText 為了排版,需要將顯示的文本內容、位置、字體、字形直接傳遞給 Quartz。相比其它 UI 組件,由于 CoreText 直接和 Quartz 來交互,所以它具有高速的排版效果。

我們來看一個CoreText對象模型圖

配圖

來一段枯燥的講解(后面會有??的)

如上圖所述,其中Framesetter對應的類型是CTFramesetter
,通過CFAttributedString(NSAttributeString 也可以無縫橋接)進行初始化,它作為CTFrame對象的生產工廠,負責根據path生產對應的CTFrame。CTFrame是可以通過CTFrameDraw函數直接繪制到context上的,當然你可以在繪制之前,操作CTFrame中的CTLine,進行一些參數的微調。CTLine 可以看做Core Text繪制中的一行的對象 通過它可以獲得當前行的line ascent,line descent ,line leading,還可以獲得Line下的所有Glyph Runs。CTRun 或者叫做 Glyph Run,是一組共享相同attributes(屬性)的字形的集合體。CTFrame是指整個該UIView子控件的繪制區域,CTLine則是指每一行,CTRun則是每一段具有一樣屬性的字符串。比如某段字體大小、顏色都一致的字符串為一個CTRun,CTRun不可以跨行,不管屬性一致或不一致。通常的結構是每一個CTFrame有多個CTLine,每一個CTLine有多個CTRun。

由于CoreText一開始便是定位于桌面的排版系統,所以使用了傳統的原點在左下角的坐標系,所以它在繪制文本的時候都是參照左下角的原點進行繪制的。

配圖

如果你啥也不做處理,直接在這個context上進行CoreText繪制,你會發現文字是鏡像且上下顛倒。

??來啦!先來看一個最簡單的CoreText使用的例子

一個簡單的例子

首先新建一個CTView繼承自UIView

import UIKit

class CTView: UIView {

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)
        
        // 1
        let context = UIGraphicsGetCurrentContext()
        
        // 2
        CGContextSetTextMatrix(context, CGAffineTransformIdentity)
        CGContextTranslateCTM(context, 0, self.bounds.size.height)
        CGContextScaleCTM(context, 1.0, -1.0)
        
        // 3
        let path = CGPathCreateMutable()
        CGPathAddRect(path, nil, self.bounds)
        
        // 4
        let attrString = NSAttributedString(string:"Hello CoreText!")
        let framesetter = CTFramesetterCreateWithAttributedString(attrString)
        let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, nil)
        
        // 5
        CTFrameDraw(frame,context!)
    }
}

然后把這個view加在ViewController上

let ctView = CTView()
ctView.frame = CGRectMake(10, 150, self.view.bounds.width - 20, 200)
ctView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(ctView)

運行效果:

配圖

解釋:
1、 通過UIGraphisGetCurrentContext()獲取當前的環境
2、將坐標系上下翻轉。對于底層的繪制引擎來說,屏幕的左下角是(0, 0)坐標。而對于上層的 UIKit 來說,左上角是 (0, 0) 坐標。所以我們為了之后的坐標系描述按 UIKit 來做,所以先在這里做一個坐標系的上下翻轉操作。翻轉之后,底層和上層的 (0, 0) 坐標就是重合的了。
3、創建繪制區域CGPathCreateMutable(),CoreText 本身支持各種文字排版的區域,我們這里簡單地將 UIView 的整個界面作為排版的區域。

當然這里如果覺得CGMutablePath 不好用 可以選擇使用更方便的UIBezierPath來操作排版區域.

把上文中的3改成

let path1 = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.bounds.size.width/2 )

把4改成 順便給文字加了點屬性

 // 4
let attrString = "Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!"
        
let mutableAttrStr = NSMutableAttributedString(string: attrString)
mutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize(20),
        NSForegroundColorAttributeName:UIColor.redColor() ], range: NSMakeRange(0, 20))
mutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize(13),NSUnderlineStyleAttributeName: 1 ], range: NSMakeRange(20,18))
let framesetter = CTFramesetterCreateWithAttributedString(mutableAttrStr)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, mutableAttrStr.length), path1.CGPath, nil)

排版區域就變了 而且使用了UIBezierPath的API

配圖

4、根據AttributedString生成CTFramesetterRef,根據framesetter和繪圖區域創建CTFrame

5、使用CTFrameDraw進行繪制(后面復雜的可能不會直接畫frame 而是選擇一行一行的畫 )

圖文混排

CoreText如果只能定義文本繪制區域,那就太沒勁了,CoreText還可以支持圖文混排,本地圖片和網絡圖片。

CoreText從繪制純文本到繪制圖片,依然是使用NSAttributedString,只不過圖片的實現方式是用一個空白字符作為在NSAttributedString中的占位符,然后設置代理,告訴CoreText給該占位字符留出一定的寬高。最后把圖片繪制到預留的位置上。

配圖

圖中 第一個圖片是存在本地,第二個圖片是來自網絡。下面看看怎么做。代碼有點長 但是思路應該清晰。

import UIKit

class CTPicTxtView: UIView {

    var image:UIImage?
    
    override func drawRect(rect: CGRect) {
        super.drawRect(rect)
        
        // 1 獲取上下文
        let context = UIGraphicsGetCurrentContext()
        
        // 2 轉換坐標
        CGContextSetTextMatrix(context, CGAffineTransformIdentity)
        CGContextTranslateCTM(context, 0, self.bounds.size.height)
        CGContextScaleCTM(context, 1.0, -1.0)
        
        // 3 繪制區域
        let path = UIBezierPath(rect: rect)
        
        // 4 創建需要繪制的文字
        let attrString = "Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!Hello CoreText!"
        
        let mutableAttrStr = NSMutableAttributedString(string: attrString)
        mutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize(20),
            NSForegroundColorAttributeName:UIColor.redColor() ], range: NSMakeRange(0, 5))
        mutableAttrStr.addAttributes([NSFontAttributeName:UIFont.systemFontOfSize(13),NSUnderlineStyleAttributeName: 1 ], range: NSMakeRange(3,10))
        let style = NSMutableParagraphStyle()   //用來設置段落樣式
        style.lineSpacing = 6 //行間距
        mutableAttrStr.addAttributes([NSParagraphStyleAttributeName:style], range: NSMakeRange(0, mutableAttrStr.length))
        
        // 5 為圖片設置CTRunDelegate,delegate決定留給圖片的空間大小
        var imageName = "mc"
        var  imageCallback =  CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (refCon) -> Void in
            
            }, getAscent: { ( refCon) -> CGFloat in
                
//                let imageName = "mc"
//                refCon.initialize()
//                let image = UIImage(named: imageName)
                return 100  //返回高度
                
            }, getDescent: { (refCon) -> CGFloat in
                
                return 50  //返回底部距離
                
            }) { (refCon) -> CGFloat in
                
//                let imageName = String("mc")
//                let image = UIImage(named: imageName)
                return 100  //返回寬度
                
        }
        let runDelegate  = CTRunDelegateCreate(&imageCallback, &imageName)
        let imgString = NSMutableAttributedString(string: " ")  // 空格用于給圖片留位置
        imgString.addAttribute(kCTRunDelegateAttributeName as String, value: runDelegate!, range: NSMakeRange(0, 1))  //rundelegate  占一個位置
        imgString.addAttribute("imageName", value: imageName, range: NSMakeRange(0, 1))//添加屬性,在CTRun中可以識別出這個字符是圖片
        mutableAttrStr.insertAttributedString(imgString, atIndex: 15)
        
        
        //網絡圖片相關
        var  imageCallback1 =  CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (refCon) -> Void in
            
            }, getAscent: { ( refCon) -> CGFloat in
                return 70  //返回高度
                
            }, getDescent: { (refCon) -> CGFloat in
                
                return 50  //返回底部距離
                
            }) { (refCon) -> CGFloat in
                return 100  //返回寬度
                
        }
        var imageUrl = "http://img3.3lian.com/2013/c2/64/d/65.jpg" //網絡圖片鏈接
        let urlRunDelegate  = CTRunDelegateCreate(&imageCallback1, &imageUrl)
        let imgUrlString = NSMutableAttributedString(string: " ")  // 空格用于給圖片留位置
        imgUrlString.addAttribute(kCTRunDelegateAttributeName as String, value: urlRunDelegate!, range: NSMakeRange(0, 1))  //rundelegate  占一個位置
        imgUrlString.addAttribute("urlImageName", value: imageUrl, range: NSMakeRange(0, 1))//添加屬性,在CTRun中可以識別出這個字符是圖片
        mutableAttrStr.insertAttributedString(imgUrlString, atIndex: 50)
        
        
        // 6 生成framesetter
        let framesetter = CTFramesetterCreateWithAttributedString(mutableAttrStr)
        let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, mutableAttrStr.length), path.CGPath, nil)
        
        // 7 繪制除圖片以外的部分
        CTFrameDraw(frame,context!)
        
        // 8 處理繪制圖片邏輯
        let lines = CTFrameGetLines(frame) as NSArray //存取frame中的ctlines
        

        let ctLinesArray = lines as Array
        var originsArray = [CGPoint](count:ctLinesArray.count, repeatedValue: CGPointZero)
        let range: CFRange = CFRangeMake(0, 0)
        CTFrameGetLineOrigins(frame,range,&originsArray)
        
        //遍歷CTRun找出圖片所在的CTRun并進行繪制,每一行可能有多個
        for i in 0..<lines.count{
            //遍歷每一行CTLine
            let line = lines[i]
            var lineAscent = CGFloat()
            var lineDescent = CGFloat()
            var lineLeading = CGFloat()
            //該函數除了會設置好ascent,descent,leading之外,還會返回這行的寬度
            CTLineGetTypographicBounds(line as! CTLineRef, &lineAscent, &lineDescent, &lineLeading)
            
            let runs = CTLineGetGlyphRuns(line as! CTLine) as NSArray
            for j in 0..<runs.count{
                // 遍歷每一個CTRun
                var  runAscent = CGFloat()
                var  runDescent = CGFloat()
                let  lineOrigin = originsArray[i]// 獲取該行的初始坐標
                let run = runs[j] // 獲取當前的CTRun
                let attributes = CTRunGetAttributes(run as! CTRun) as NSDictionary
            
                let width =  CGFloat( CTRunGetTypographicBounds(run as! CTRun, CFRangeMake(0,0), &runAscent, &runDescent, nil))
                
                let  runRect = CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line as! CTLine, CTRunGetStringRange(run as! CTRun).location, nil), lineOrigin.y - runDescent, width, runAscent + runDescent)
                let imageNames = attributes.objectForKey("imageName")
                let urlImageName = attributes.objectForKey("urlImageName")
                
                if imageNames is NSString {
                    //本地圖片
                    let image = UIImage(named: imageName as String)
                    let imageDrawRect = CGRectMake(runRect.origin.x, lineOrigin.y-runDescent, 100, 100)
                    CGContextDrawImage(context, imageDrawRect, image?.CGImage)
                }
                
                if let urlImageName = urlImageName as? String{
                    var image:UIImage?
                    let imageDrawRect = CGRectMake(runRect.origin.x, lineOrigin.y-runDescent, 100, 100)
                    if self.image == nil{
                        image = UIImage(named:"hs") //灰色圖片占位
                        //去下載
                        if let url = NSURL(string: urlImageName){
                            let request = NSURLRequest(URL: url)
                            NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, resp, err) -> Void in
                                
                                if let data = data{
                                    dispatch_sync(dispatch_get_main_queue(), { () -> Void in
                                        self.image = UIImage(data: data)
                                        self.setNeedsDisplay()  //下載完成會重繪
                                    })
                                    
                                }
                            }).resume()
                        }

                    }else{
                        image = self.image
                    }
                    CGContextDrawImage(context, imageDrawRect, image?.CGImage)
                }
            }
        }
        
    }
    
}

1、2、3、4和前面簡單Demo是一模一樣的 ,只是加了個行間距。

5、 這塊用一個回調設置了圖片大小等信息 ,然會創建了一個CTRun的代理,創建一個空白占位字符,給它加了個屬性,后面繪制的時候好識別。 最后把這個占位符加到我們屬性文本的某個位置。

地下網絡圖片 name換成了url 其他如法炮制

6、7和上小結一樣的

8、 正式開始處理圖片部分

根據CTFrame 獲取 CTLine 獲取 originsArray 每一行的原點,用來定位 。根據CTLine獲取到 CTRun 。 CTRun是每一個相同屬性字符串 ,但是不會隔行。

遍歷CTRun 根據我們前面設置的屬性 找到本地圖片進行繪制

網絡圖片的繪制也很簡單如果沒有下載 先放個灰色的圖占位,然后去下載,下載好了 賦值給self的一個變量 ,然后重繪就OK了 關于NSURLSession不會使用的可以看我的另一篇文章。NSURLSession

關于CoreText還有很多,逐行排版 文字和emoji 混排問題 , 連接識別 ,點擊圖片 點擊連接等等。。篇幅有點長。。下一篇接著介紹,這篇先到這邊。

本文實例代碼已上傳github: https://github.com/smalldu/ZZCoreTextDemo

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

推薦閱讀更多精彩內容