Core Text框架詳細解析(四) —— Core Text文本布局操作

版本記錄

版本號 時間
V1.0 2017.10.22

前言

Core Text框架主要用來做文字處理,是的iOS3.2+OSX10.5+中的文本引擎,讓您精細的控制文本布局和格式。它位于在UIKit中和CoreGraphics/Quartz之間的最佳點。接下來這幾篇我們就主要解析該框架。感興趣的可以前面幾篇。
1. Core Text框架詳細解析(一) —— 基本概覽
2. Core Text框架詳細解析(二) —— 關(guān)于Core Text
3. Core Text框架詳細解析(三) —— Core Text總體概覽

字體布局操作

本章介紹一些常見的文本布局操作,并顯示如何使用Core Text執(zhí)行它們。 本章包含以下代碼列表操作:


Laying Out a Paragraph - 布局一個段落

排版typesetting中最常見的操作之一是在任意大小的矩形區(qū)域內(nèi)布置多行段落。 Core Text使此操作變得容易,只需要幾行Core Text特定的代碼。 要布置該段落,您需要繪制圖形上下文,一個矩形路徑來提供文本布局的區(qū)域,以及一個屬性字符串。 此示例中的大多數(shù)代碼是創(chuàng)建和初始化上下文,路徑和字符串。 完成之后,Core Text只需要三行代碼來進行布局。

Listing 2-1中的代碼顯示了段落的布局方式。 該代碼可以駐留在UIView子類(OS X中的NSView子類)的drawRect:方法中。

// Listing 2-1  Typesetting a simple paragraph

// Initialize a graphics context in iOS.

CGContextRef context = UIGraphicsGetCurrentContext();
 
// Flip the context coordinates, in iOS only.
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
 
// Initializing a graphic context in OS X is different:
// CGContextRef context =
//     (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
 
// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
 
// Create a path which bounds the area where you will be drawing text.
// The path need not be rectangular.
CGMutablePathRef path = CGPathCreateMutable();
 
// In this simple example, initialize a rectangular path.
CGRect bounds = CGRectMake(10.0, 10.0, 200.0, 200.0);
CGPathAddRect(path, NULL, bounds );
 
// Initialize a string.
CFStringRef textString = CFSTR("Hello, World! I know nothing in the world that has as much power as a word. Sometimes I write one, and I look at it, until it begins to shine.");
 
// Create a mutable attributed string with a max length of 0.
// The max length is a hint as to how much internal storage to reserve.
// 0 means no hint.
CFMutableAttributedStringRef attrString =
         CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
 
// Copy the textString into the newly created attrString
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),
         textString);
 
// Create a color that will be added as an attribute to the attrString.
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };
CGColorRef red = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);
 
// Set the color of the first 12 chars to red.
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 12),
         kCTForegroundColorAttributeName, red);
 
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter =
         CTFramesetterCreateWithAttributedString(attrString);
CFRelease(attrString);
 
// Create a frame.
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
          CFRangeMake(0, 0), path, NULL);
 
// Draw the specified frame in the given context.
CTFrameDraw(frame, context);
 
// Release the objects we used.
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);

Simple Text Label - 簡單文本標(biāo)簽

另一個常見的排版typesetting操作是繪制一行文本以用作用戶界面Label的文本。 在Core Text中,這只需要兩行代碼:一個用CFAttributedString創(chuàng)建行對象,另一個用于將該行繪制到圖形上下文中。

Listing 2-2 顯示了如何在UIView或NSView子類的drawRect:方法中完成此操作。 該列表省略了在本文檔中其他列表中顯示的純文本字符串,字體、圖形上下文和操作的初始化。 它顯示如何創(chuàng)建屬性字典并使用它來創(chuàng)建屬性字符串。 (字體創(chuàng)建顯示在 Creating Font DescriptorsCreating a Font from a Font Descriptor。)

// Listing 2-2  Typesetting a simple text label

CFStringRef string; CTFontRef font; CGContextRef context;

// Initialize the string, font, and context
 
CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { font };
 
CFDictionaryRef attributes =
    CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
        (const void**)&values, sizeof(keys) / sizeof(keys[0]),
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks);
 
CFAttributedStringRef attrString =
    CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
CFRelease(string);
CFRelease(attributes);
 
CTLineRef line = CTLineCreateWithAttributedString(attrString);
 
// Set text position and draw the line into the graphics context
CGContextSetTextPosition(context, 10.0, 10.0);
CTLineDraw(line, context);
CFRelease(line);

Columnar Layout - 柱狀布局

在多列中布局文本是另一種常見的排版typesetting操作。嚴格來說,Core Text本身一次只列出一列,不計算列大小或位置。您可以在調(diào)用Core Text之前進行這些操作,以便在您計算的路徑區(qū)域中布置文本。在此示例中,Core Text除了在每列中布置文本之外,還為每列提供了文本字符串中的子范圍。

Listing 2-3 中的createColumnsWithColumnCount:方法接受要繪制的列數(shù)的參數(shù),并返回一個路徑數(shù)組,每列的一個路徑。

Listing 2-4 包括drawRect:方法的一個實現(xiàn),它調(diào)用本列表中首先定義的本地createColumnsWithColumnCount方法。該代碼駐留在UIView子類(OS X中的NSView子類)中。該子類包含一個attributString屬性,此屬性在此處未顯示,但在此列表中調(diào)用其訪問器以返回要排列的屬性字符串。

// Listing 2-3  Dividing a view into columns

- (CFArrayRef)createColumnsWithColumnCount:(int)columnCount
{
    int column;
 
    CGRect* columnRects = (CGRect*)calloc(columnCount, sizeof(*columnRects));
    // Set the first column to cover the entire view.
    columnRects[0] = self.bounds;
 
    // Divide the columns equally across the frame's width.
    CGFloat columnWidth = CGRectGetWidth(self.bounds) / columnCount;
    for (column = 0; column < columnCount - 1; column++) {
        CGRectDivide(columnRects[column], &columnRects[column],
                     &columnRects[column + 1], columnWidth, CGRectMinXEdge);
    }
 
   // Inset all columns by a few pixels of margin.
    for (column = 0; column < columnCount; column++) {
        columnRects[column] = CGRectInset(columnRects[column], 8.0, 15.0);
    }
 
    // Create an array of layout paths, one for each column.
    CFMutableArrayRef array =
                     CFArrayCreateMutable(kCFAllocatorDefault,
                                  columnCount, &kCFTypeArrayCallBacks);
 
    for (column = 0; column < columnCount; column++) {
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, columnRects[column]);
        CFArrayInsertValueAtIndex(array, column, path);
        CFRelease(path);
    }
    free(columnRects);
    return array;
}
// Listing 2-4  Performing columnar text layout

// Override drawRect: to draw the attributed string into columns.
// (In OS X, the drawRect: method of NSView takes an NSRect parameter,
//  but that parameter is not used in this listing.)
- (void)drawRect:(CGRect)rect
{
    // Initialize a graphics context in iOS.
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    // Flip the context coordinates in iOS only.
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
 
    // Initializing a graphic context in OS X is different:
    // CGContextRef context =
    //     (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
 
    // Set the text matrix.
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
 
    // Create the framesetter with the attributed string.
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(
                                      (CFAttributedStringRef)self.attributedString);
 
    // Call createColumnsWithColumnCount function to create an array of
    // three paths (columns).
    CFArrayRef columnPaths = [self createColumnsWithColumnCount:3];
 
    CFIndex pathCount = CFArrayGetCount(columnPaths);
    CFIndex startIndex = 0;
    int column;
 
    // Create a frame for each column (path).
    for (column = 0; column < pathCount; column++) {
        // Get the path for this column.
        CGPathRef path = (CGPathRef)CFArrayGetValueAtIndex(columnPaths, column);
 
        // Create a frame for this column and draw it.
        CTFrameRef frame = CTFramesetterCreateFrame(
                             framesetter, CFRangeMake(startIndex, 0), path, NULL);
        CTFrameDraw(frame, context);
 
        // Start the next frame at the first character not visible in this frame.
        CFRange frameRange = CTFrameGetVisibleStringRange(frame);
        startIndex += frameRange.length;
        CFRelease(frame);
 
    }
    CFRelease(columnPaths);
    CFRelease(framesetter);
 
}

Manual Line Breaking - 手動換行

在Core Text中,您通常不需要進行手動換行,除非您有特殊的連字過程或類似的要求。 Framesetter會自動執(zhí)行換行。 或者,Core Text使您能夠準(zhǔn)確地指定每行文本中斷的位置。 Listing 2-5顯示了如何創(chuàng)建一個排版機typesetter,一個由framesetter使用的對象,并直接使用排版機來找到適當(dāng)?shù)膿Q行符并手動創(chuàng)建一個排版行。 此示例還顯示了繪制之前如何居中的一行。

該代碼可以在UIView子類的drawRect:方法(OS X中的NSView子類)中。 列表不顯示代碼中使用的變量的初始化。

// Listing 2-5  Performing manual line breaking

double width; CGContextRef context; CGPoint textPosition; CFAttributedStringRef attrString;
// Initialize those variables.
 
// Create a typesetter using the attributed string.
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(attrString);
 
// Find a break for line from the beginning of the string to the given width.
CFIndex start = 0;
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);
 
// Use the returned character count (to the break) to create the line.
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));
 
// Get the offset needed to center the line.
float flush = 0.5; // centered
double penOffset = CTLineGetPenOffsetForFlush(line, flush, width);
 
// Move the given text drawing position by the calculated offset and draw the line.
CGContextSetTextPosition(context, textPosition.x + penOffset, textPosition.y);
CTLineDraw(line, context);
 
// Move the index beyond the line break.
start += count;

Applying a Paragraph Style - 應(yīng)用段落樣式

Listing 2-6實現(xiàn)了一個將段落樣式應(yīng)用于屬性符串的函數(shù)。 該函數(shù)接受字體名稱,點大小和增加或減少文本行之間的空格量的行間距的參數(shù)。 該函數(shù)由Listing 2-7中的代碼調(diào)用,它創(chuàng)建一個純文本字符串,使用applyParaStyle函數(shù)創(chuàng)建一個帶有給定段落屬性的屬性字符串,然后創(chuàng)建一個framesetterframe,并繪制frame。

// Listing 2-6  Applying a paragraph style

NSAttributedString* applyParaStyle(
                CFStringRef fontName , CGFloat pointSize,
                NSString *plainText, CGFloat lineSpaceInc){
 
    // Create the font so we can determine its height.
    CTFontRef font = CTFontCreateWithName(fontName, pointSize, NULL);
 
    // Set the lineSpacing.
    CGFloat lineSpacing = (CTFontGetLeading(font) + lineSpaceInc) * 2;
 
    // Create the paragraph style settings.
    CTParagraphStyleSetting setting;
 
    setting.spec = kCTParagraphStyleSpecifierLineSpacing;
    setting.valueSize = sizeof(CGFloat);
    setting.value = &lineSpacing;
 
    CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(&setting, 1);
 
    // Add the paragraph style to the dictionary.
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                               (__bridge id)font, (id)kCTFontNameAttribute,
                               (__bridge id)paragraphStyle,
                               (id)kCTParagraphStyleAttributeName, nil];
    CFRelease(font);
    CFRelease(paragraphStyle);
 
    // Apply the paragraph style to the string to created the attributed string.
    NSAttributedString* attrString = [[NSAttributedString alloc]
                               initWithString:(NSString*)plainText
                               attributes:attributes];
 
    return attrString;
}

Listing 2-7中,styled字符串用于創(chuàng)建一個framesetter,代碼用framesetter創(chuàng)建一個frame并繪制frame。

// Listing 2-7  Drawing the styled paragraph

- (void)drawRect:(CGRect)rect {
    // Initialize a graphics context in iOS.
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    // Flip the context coordinates in iOS only.
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
 
    // Set the text matrix.
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
 
    CFStringRef fontName = CFSTR("Didot Italic");
    CGFloat pointSize = 24.0;
 
    CFStringRef string = CFSTR("Hello, World! I know nothing in the world that has
                                   as much power as a word. Sometimes I write one,
                                   and I look at it, until it begins to shine.");
 
    // Apply the paragraph style.
    NSAttributedString* attrString = applyParaStyle(fontName, pointSize, string, 50.0);
 
    // Put the attributed string with applied paragraph style into a framesetter.
    CTFramesetterRef framesetter =
             CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
 
    // Create a path to fill the View.
    CGPathRef path = CGPathCreateWithRect(rect, NULL);
 
    // Create a frame in which to draw.
    CTFrameRef frame = CTFramesetterCreateFrame(
                                    framesetter, CFRangeMake(0, 0), path, NULL);
 
    // Draw the frame.
    CTFrameDraw(frame, context);
    CFRelease(frame);
    CGPathRelease(path);
    CFRelease(framesetter);
}

在OS X中,NSView drawRect:方法接收NSRect參數(shù),但CGPathCreateWithRect函數(shù)需要一個CGRect參數(shù)。 因此,必須使用以下函數(shù)調(diào)用將NSRect對象轉(zhuǎn)換為CGRect對象:

CGRect myRect = NSRectToCGRect([self bounds]);

此外,在OS X中,您可以不同地獲取圖形上下文,并且不會翻轉(zhuǎn)其坐標(biāo),如Listing 2-7中的注釋所示。


Displaying Text in a Nonrectangular Region - 在非矩形區(qū)域中顯示文本

在非矩形區(qū)域中顯示文本的困難部分是描述非矩形路徑。 Listing 2-8 中的AddSquashedDonutPath函數(shù)返回一個環(huán)形路徑。 一旦你有路徑,只需調(diào)用通常的Core Text函數(shù)來應(yīng)用屬性和繪制。

// Listing 2-8  Displaying text in a nonrectangular path
 
// Create a path in the shape of a donut.
static void AddSquashedDonutPath(CGMutablePathRef path,
              const CGAffineTransform *m, CGRect rect)
{
    CGFloat width = CGRectGetWidth(rect);
    CGFloat height = CGRectGetHeight(rect);
 
    CGFloat radiusH = width / 3.0;
    CGFloat radiusV = height / 3.0;
 
    CGPathMoveToPoint( path, m, rect.origin.x, rect.origin.y + height - radiusV);
    CGPathAddQuadCurveToPoint( path, m, rect.origin.x, rect.origin.y + height,
                               rect.origin.x + radiusH, rect.origin.y + height);
    CGPathAddLineToPoint( path, m, rect.origin.x + width - radiusH,
                               rect.origin.y + height);
    CGPathAddQuadCurveToPoint( path, m, rect.origin.x + width,
                               rect.origin.y + height,
                               rect.origin.x + width,
                               rect.origin.y + height - radiusV);
    CGPathAddLineToPoint( path, m, rect.origin.x + width,
                               rect.origin.y + radiusV);
    CGPathAddQuadCurveToPoint( path, m, rect.origin.x + width, rect.origin.y,
                               rect.origin.x + width - radiusH, rect.origin.y);
    CGPathAddLineToPoint( path, m, rect.origin.x + radiusH, rect.origin.y);
    CGPathAddQuadCurveToPoint( path, m, rect.origin.x, rect.origin.y,
                               rect.origin.x, rect.origin.y + radiusV);
    CGPathCloseSubpath( path);
 
    CGPathAddEllipseInRect( path, m,
                            CGRectMake( rect.origin.x + width / 2.0 - width / 5.0,
                            rect.origin.y + height / 2.0 - height / 5.0,
                            width / 5.0 * 2.0, height / 5.0 * 2.0));
}
 
// Generate the path outside of the drawRect call so the path is calculated only once.
- (NSArray *)paths
{
    CGMutablePathRef path = CGPathCreateMutable();
    CGRect bounds = self.bounds;
    bounds = CGRectInset(bounds, 10.0, 10.0);
    AddSquashedDonutPath(path, NULL, bounds);
 
    NSMutableArray *result =
              [NSMutableArray arrayWithObject:CFBridgingRelease(path)];
    return result;
}
 
- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
 
    // Initialize a graphics context in iOS.
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    // Flip the context coordinates in iOS only.
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
 
    // Set the text matrix.
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
 
    // Initialize an attributed string.
    CFStringRef textString = CFSTR("Hello, World! I know nothing in the world that
    has as much power as a word. Sometimes I write one, and I look at it,
    until it begins to shine.");
 
    // Create a mutable attributed string.
     CFMutableAttributedStringRef attrString =
                CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
 
    // Copy the textString into the newly created attrString.
    CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), textString);
 
    // Create a color that will be added as an attribute to the attrString.
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };
    CGColorRef red = CGColorCreate(rgbColorSpace, components);
    CGColorSpaceRelease(rgbColorSpace);
 
    // Set the color of the first 13 chars to red.
    CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 13),
                                     kCTForegroundColorAttributeName, red);
 
    // Create the framesetter with the attributed string.
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
 
    // Create the array of paths in which to draw the text.
    NSArray *paths = [self paths];
 
    CFIndex startIndex = 0;
 
    // In OS X, use NSColor instead of UIColor.
    #define GREEN_COLOR [UIColor greenColor]
    #define YELLOW_COLOR [UIColor yellowColor]
    #define BLACK_COLOR [UIColor blackColor]
 
    // For each path in the array of paths...
    for (id object in paths) {
        CGPathRef path = (__bridge CGPathRef)object;
 
        // Set the background of the path to yellow.
        CGContextSetFillColorWithColor(context, [YELLOW_COLOR CGColor]);
 
        CGContextAddPath(context, path);
        CGContextFillPath(context);
 
        CGContextDrawPath(context, kCGPathStroke);
 
        // Create a frame for this path and draw the text.
        CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
                                         CFRangeMake(startIndex, 0), path, NULL);
        CTFrameDraw(frame, context);
 
        // Start the next frame at the first character not visible in this frame.
        CFRange frameRange = CTFrameGetVisibleStringRange(frame);
        startIndex += frameRange.length;
        CFRelease(frame);
}
 
CFRelease(attrString);
CFRelease(framesetter);
}

后記

未完,待續(xù)~~~

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

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