Core Text 編程指南02:常見文本布局操作

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

  • 列出一個段落
  • 簡單的文字標簽
  • 列式布局
  • 手動換行
  • 應用段落樣式
  • 在非矩形區(qū)域中顯示文本

列出一個段落

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

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

清單2-1排版一個簡單的段落(下面是蘋果文檔給出的例子)

// 在iOS中初始化圖形上下文。
CGContextRef context = UIGraphicsGetCurrentContext();
// 翻轉(zhuǎn)上下文坐標,僅限iOS。
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

// 在OS X中初始化圖形上下文是不同的:
// CGContextRef context =
//     (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];

// 設置文本矩陣。
CGContextSetTextMatrix(context, CGAffineTransformIdentity);

// 創(chuàng)建一個限定要繪制文本的區(qū)域的路徑。
// 路徑不需要一定是矩形。
CGMutablePathRef path = CGPathCreateMutable();

// 在這個簡單的例子中,初始化一個矩形路徑。
CGRect bounds = CGRectMake(10.0, 10.0, 200.0, 200.0);
CGPathAddRect(path, NULL, bounds );

// 初始化一個字符串。
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.");

// 創(chuàng)建一個最大長度為0的可變屬性字符串。
// 最大長度是關(guān)于要保留多少內(nèi)部存儲空間的提示。
// 0表示沒有提示。
CFMutableAttributedStringRef attrString =
     
CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

// 將textString復制到新創(chuàng)建的attrString中
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),
     textString);

// 創(chuàng)建一個將作為屬性添加到attrString的顏色。
CGColorSpaceRef rgbColorSpace = 
CGColorSpaceCreateDeviceRGB();
CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };
CGColorRef red = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);

 // 將前12個字符的顏色設置為紅色。
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 12),
     kCTForegroundColorAttributeName, red);

// 使用屬性字符串創(chuàng)建frame集合。
CTFramesetterRef framesetter =
     
CTFramesetterCreateWithAttributedString(attrString);
CFRelease(attrString);

// 創(chuàng)建一個frame。
CTFrameRef frame = 
CTFramesetterCreateFrame(framesetter,
      CFRangeMake(0, 0), path, NULL);

// 在給定的上下文中繪制指定的幀。
CTFrameDraw(frame, context);

// 釋放我們使用的對象。
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);

下面是我的demo里面的相關(guān)代碼(swift語言):

override func draw(_ rect: CGRect) {
    super.draw(rect)
    // 在iOS中初始化圖形上下文。
    let context = UIGraphicsGetCurrentContext()
    // 翻轉(zhuǎn)上下文坐標,僅限iOS。
    context?.translateBy(x: 0, y: self.bounds.size.height)
    context?.scaleBy(x: 1.0, y: -1.0)
    // 設置文本矩陣。
    context?.textMatrix = .identity
    // 創(chuàng)建一個限定要繪制文本的區(qū)域的路徑。
    // 路徑不需要一定是矩形。
    let path = CGMutablePath()
    // 在這個簡單的例子中,初始化一個矩形路徑。
    let bounds = self.bounds
    path.addRect(bounds)
    // 初始化一個字符串。
    let string = "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."
    let textString: CFString = string as CFString
    // 創(chuàng)建一個最大長度為0的可變屬性字符串。
    // 最大長度是關(guān)于要保留多少內(nèi)部存儲空間的提示。
    // 0表示沒有提示。
    let attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)
    // 將textString復制到新創(chuàng)建的attrString中
    CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), textString)
    // 創(chuàng)建一個將作為屬性添加到attrString的顏色。
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let components: Array<CGFloat> = [1.0,1.0, 1.0, 0.8]
    let red = CGColor(colorSpace: rgbColorSpace, components: components)
    // 將前12個字符的顏色設置為紅色。
    CFAttributedStringSetAttribute(attrString, CFRangeMake(0, string.count), NSAttributedString.Key.foregroundColor as CFString, red)
    // 使用屬性字符串創(chuàng)建frame集合。
    let framesetter = CTFramesetterCreateWithAttributedString(attrString!)
    // 創(chuàng)建一個frame
    let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)
    // 在給定的上下文中繪制指定的幀。
    CTFrameDraw(frame, context!)
}

簡單的文字標簽

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

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

清單2-2排版一個簡單的文本標簽

CFStringRef string; CTFontRef font; CGContextRef 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);

// 設置文本位置并將線條繪制到圖形上下文中

CGContextSetTextPosition(context, 10.0, 10.0);
CTLineDraw(line, context);
CFRelease(line);

下面是我自己的demo中的設置(swift語言)

override func draw(_ rect: CGRect) {
    super.draw(rect)
    let text = "Test content Test content Test "
    let string: CFString =  text as CFString
    let font: CTFont = CTFontCreateWithName(string, 50, nil)
    let context = UIGraphicsGetCurrentContext()
    context?.translateBy(x: 0, y: 60)
    context?.scaleBy(x: 1.0, y: -1.0)
    //初始化字符串,字體,和上下文
    let keys: [CFString] = [kCTFontAttributeName]
    let keybuffer = buffer(to: CFString.self, source: keys)
    let values: [CTFont] = [font]
    let valueBuffer = buffer(to: CTFont.self, source: values)
    var keyCallbacks = kCFTypeDictionaryKeyCallBacks
    var valueCallbacks = kCFTypeDictionaryValueCallBacks
    let attributes = CFDictionaryCreate(kCFAllocatorDefault, UnsafeMutablePointer<UnsafeRawPointer?>(keybuffer), UnsafeMutablePointer<UnsafeRawPointer?>(valueBuffer), 0, &keyCallbacks, &valueCallbacks)
    let attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes)
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let components: Array<CGFloat> = [1.0,1.0, 1.0, 0.8]
    let red = CGColor(colorSpace: rgbColorSpace, components: components)
    CFAttributedStringSetAttribute((attrString as! CFMutableAttributedString), CFRangeMake(0, text.count), NSAttributedString.Key.foregroundColor as CFString, red)
    let line = CTLineCreateWithAttributedString(attrString!)
    context?.textPosition = CGPoint(x: 5, y: 50)
    CTLineDraw(line, context!)
}

// UnsafeMutablePointer<UnsafeRawPointer?>類型數(shù)據(jù)創(chuàng)建
fileprivate func buffer<T>(to type:T.Type, source:[T]) -> UnsafeMutablePointer<UnsafeRawPointer?>{
    let buffer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: source.count)
    for idx in 0..<source.count {
        let m_ptr = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<T>.size, alignment: MemoryLayout<T>.alignment)
        let bindptr = m_ptr.bindMemory(to: type, capacity: 1)
        bindptr.initialize(to: source[idx])
        let pty = UnsafeRawPointer(m_ptr)
        buffer.advanced(by: idx).pointee = pty
    }
    return buffer
}

列式布局

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

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

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

清單2-3將視圖劃分為列

- (CFArrayRef)createColumnsWithColumnCount:(int)columnCount
{

int column;

 CGRect* columnRects = (CGRect*)calloc(columnCount, sizeof(*columnRects));
// 設置第一列以覆蓋整個視圖。
columnRects[0] = self.bounds;

// 在框架的寬度上平均分配列。
CGFloat columnWidth = CGRectGetWidth(self.bounds) / columnCount;
for (column = 0; column < columnCount - 1; column++) {
    CGRectDivide(columnRects[column], &columnRects[column],
                 &columnRects[column + 1], columnWidth, CGRectMinXEdge);
}

// 將所有列插入幾個像素的邊距。
for (column = 0; column < columnCount; column++) {
    columnRects[column] = CGRectInset(columnRects[column], 8.0, 15.0);
}

// 創(chuàng)建一個布局路徑數(shù)組,每列一個。
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;
}

清單2-4執(zhí)行列式文本布局

// Override drawRect: 將屬性字符串繪制到列中。
// (在OS X中,NSView的drawRect:方法采用NSRect參數(shù),
//  但該列表中未使用該參數(shù)。)
- (void)drawRect:(CGRect)rect{
 // 在iOS中初始化圖形上下文。
CGContextRef context = UIGraphicsGetCurrentContext();

// 僅在iOS中翻轉(zhuǎn)上下文坐標。
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

// 在OS X中初始化圖形上下文是不同的:
// CGContextRef context =
//     (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];

// 設置文本矩陣。
CGContextSetTextMatrix(context, CGAffineTransformIdentity);

// 使用屬性字符串創(chuàng)建框架集。
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(
                                  (CFAttributedStringRef)self.attributedString);

// 調(diào)用createColumnsWithColumnCount函數(shù)來創(chuàng)建一個數(shù)組
// 三條路徑(列)。
CFArrayRef columnPaths = [self createColumnsWithColumnCount:3];

CFIndex pathCount = CFArrayGetCount(columnPaths);
CFIndex startIndex = 0;
int column;

// 為每列(路徑)創(chuàng)建一個框架。
for (column = 0; column < pathCount; column++) {
    //獲取此列的路徑。
    CGPathRef path = (CGPathRef)CFArrayGetValueAtIndex(columnPaths, column);

    //為此列創(chuàng)建一個框架并繪制它。
    CTFrameRef frame = CTFramesetterCreateFrame(
                         framesetter, CFRangeMake(startIndex, 0), path, NULL);
    CTFrameDraw(frame, context);

    //在此框架中不可見的第一個字符處開始下一幀。
    CFRange frameRange = CTFrameGetVisibleStringRange(frame);
    startIndex += frameRange.length;
    CFRelease(frame);

}
CFRelease(columnPaths);
CFRelease(framesetter);
}

手動換行

Core Text中,除非您有特殊的連字過程或類似要求,否則通常不需要手動換行。framesetter自動執(zhí)行換行。或者,Core Text使你可以準確指定每行文本的中斷位置。清單2-5顯示了如何創(chuàng)建排版工具,框架集使用的對象,以及直接使用排版工具查找適當?shù)膿Q行符并手動創(chuàng)建排版行。此示例還顯示了在繪制之前如何使線居中。

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

清單2-5執(zhí)行手動換行

double width; CGContextRef context; CGPoint textPosition; CFAttributedStringRef attrString;
// 初始化這些變量。

// 使用屬性字符串創(chuàng)建排字機。
CTTypesetterRef typesetter = 
CTTypesetterCreateWithAttributedString(attrString);

// 找到從字符串開頭到給定寬度的行的中斷。
CFIndex start = 0;
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);


// 使用返回的字符數(shù)(到中斷)來創(chuàng)建該行。
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));

// 獲得使線條居中所需的偏移量。
float flush = 0.5; // centered
double penOffset = CTLineGetPenOffsetForFlush(line, flush, width);

// 按計算的偏移量移動給定的文本繪圖位置并繪制線條。
CGContextSetTextPosition(context, textPosition.x + penOffset, textPosition.y);
CTLineDraw(line, context);

// 將索引移到換行符之外。
start += count;

下面是我自己的代碼(swift):

// 執(zhí)行手動換行
override func draw(_ rect: CGRect) {
    super.draw(rect)
    // 在iOS中初始化圖形上下文。
    let context = UIGraphicsGetCurrentContext()
    // 翻轉(zhuǎn)上下文坐標,僅限iOS。
    context?.translateBy(x: 0, y: self.bounds.size.height)
    context?.scaleBy(x: 1.0, y: -1.0)
    // 設置文本矩陣。
    context?.textMatrix = .identity
    // 創(chuàng)建一個限定要繪制文本的區(qū)域的路徑。
    // 路徑不需要一定是矩形。
    let path = CGMutablePath()
    // 在這個簡單的例子中,初始化一個矩形路徑。
    let bounds = self.bounds
    path.addRect(bounds)
    // 初始化一個字符串。
    let string = "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."
    let textString: CFString = string as CFString
    // 創(chuàng)建一個最大長度為0的可變屬性字符串。
    // 最大長度是關(guān)于要保留多少內(nèi)部存儲空間的提示。
    // 0表示沒有提示。
    let attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)
    // 將textString復制到新創(chuàng)建的attrString中
    CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), textString)
    // 創(chuàng)建一個將作為屬性添加到attrString的顏色。
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let components: Array<CGFloat> = [1.0,1.0, 1.0, 0.8]
    let white = CGColor(colorSpace: rgbColorSpace, components: components)
    // 將字符的顏色設置為白色。
    CFAttributedStringSetAttribute(attrString, CFRangeMake(0, string.count), NSAttributedString.Key.foregroundColor as CFString, white)
    let width: Double = 150
    let textPosition: CGPoint = CGPoint(x: 0, y: 200)
    let font: CTFont = CTFontCreateWithName(textString, 22.0, nil)
    CFAttributedStringSetAttribute(attrString, CFRangeMake(0, string.count),  NSAttributedString.Key.font as CFString, font)
    // 使用屬性字符串創(chuàng)建排字機。
    let typesetter = CTTypesetterCreateWithAttributedString(attrString!)
    // 找到從字符串開頭到給定寬度的行的中斷。
    var start: CFIndex = 10
    let count: CFIndex = CTTypesetterSuggestLineBreak(typesetter, start, width)
    // 使用返回的字符數(shù)(到中斷)來創(chuàng)建該行。
    let line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count))
    // 獲得使線條居中所需的偏移量。
    let flush: CGFloat = 0.5
    let penOffset: CGFloat = CGFloat(CTLineGetPenOffsetForFlush(line, flush, width))
    // 按計算的偏移量移動給定的文本繪圖位置并繪制線條。
    context?.textPosition = CGPoint(x: textPosition.x + penOffset, y: textPosition.y)
    CTLineDraw(line, context!)
    start += count
}

// UnsafeMutablePointer<UnsafeRawPointer?>類型數(shù)據(jù)創(chuàng)建
fileprivate func buffer<T>(to type:T.Type, source:[T]) -> UnsafeMutablePointer<UnsafeRawPointer?>{
    let buffer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: source.count)
    for idx in 0..<source.count {
        let m_ptr = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<T>.size, alignment: MemoryLayout<T>.alignment)
        let bindptr = m_ptr.bindMemory(to: type, capacity: 1)
        bindptr.initialize(to: source[idx])
        let pty = UnsafeRawPointer(m_ptr)
        buffer.advanced(by: idx).pointee = pty
    }
    return buffer
}

應用段落樣式

清單2-6實現(xiàn)了一個將段落樣式應用于屬性字符串的函數(shù)。該函數(shù)接受字體名稱,磅值和行間距作為參數(shù),這增加或減少文本行之間的空間量。此函數(shù)由清單2-7中的代碼調(diào)用,該代碼創(chuàng)建純文本字符串,使用applyParaStyle函數(shù)創(chuàng)建具有給定段落屬性的屬性字符串,然后創(chuàng)建frame集和frame,并繪制frame

清單2-6應用段落樣式

NSAttributedString* applyParaStyle(
            CFStringRef fontName , CGFloat pointSize,
            NSString *plainText, CGFloat lineSpaceInc){
// 創(chuàng)建字體,以便我們確定其高度。
CTFontRef font = CTFontCreateWithName(fontName, pointSize, NULL);

// 設置行間距。
CGFloat lineSpacing = (CTFontGetLeading(font) + lineSpaceInc) * 2;

// 創(chuàng)建段落樣式設置。
CTParagraphStyleSetting setting;

setting.spec = kCTParagraphStyleSpecifierLineSpacing;
setting.valueSize = sizeof(CGFloat);
setting.value = &lineSpacing;

CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(&setting, 1);

// 將段落樣式添加到字典中。
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                           (__bridge id)font, (id)kCTFontNameAttribute,
                           (__bridge id)paragraphStyle,
                           (id)kCTParagraphStyleAttributeName, nil];
CFRelease(font);
CFRelease(paragraphStyle);

// 將段落樣式應用于字符串以創(chuàng)建屬性字符串。
NSAttributedString* attrString = [[NSAttributedString alloc]
                           initWithString:(NSString*)plainText
                           attributes:attributes];

return attrString;
}

在清單2-7中,樣式化字符串用于創(chuàng)建框架集。代碼使用框架集創(chuàng)建框架并繪制框架。

清單2-7繪制樣式段落

- (void)drawRect:(CGRect)rect {
// 在iOS中初始化圖形上下文。
CGContextRef context = UIGraphicsGetCurrentContext();

// 僅在iOS中翻轉(zhuǎn)上下文坐標。
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

// 設置文本矩陣。
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.");

// 應用段落樣式。
NSAttributedString* attrString = applyParaStyle(fontName, pointSize, string, 50.0);

// 將帶有應用段落樣式的屬性字符串放入框架集中。
CTFramesetterRef framesetter =
         CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);

// 創(chuàng)建一個填充視圖的路徑。
CGPathRef path = CGPathCreateWithRect(rect, NULL);

// 創(chuàng)建一個繪制框架。
CTFrameRef frame = CTFramesetterCreateFrame(
                                framesetter, CFRangeMake(0, 0), path, NULL);

// 繪制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)其坐標,如清單2-7中的注釋所示。

在非矩形區(qū)域中顯示文本

在非矩形區(qū)域中顯示文本的困難部分是描述非矩形路徑。清單2-8中的AddSquashedDonutPath函數(shù)返回一個環(huán)形路徑。獲得路徑后,只需調(diào)用常用的Core Text函數(shù)即可應用屬性并繪制。

清單2-8在非矩形路徑中顯示文本

// 創(chuàng)建一個甜甜圈形狀的路徑。
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));
}

// 生成drawRect調(diào)用之外的路徑,以便僅計算路徑一次。

- (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];

// 在iOS中初始化圖形上下文。
CGContextRef context = UIGraphicsGetCurrentContext();

// 僅在iOS中翻轉(zhuǎn)上下文坐標。
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

// 設置文本矩陣。
CGContextSetTextMatrix(context, CGAffineTransformIdentity);

// 初始化屬性字符串。
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.");

// 創(chuàng)建一個可變的屬性字符串。
 CFMutableAttributedStringRef attrString =
            CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

// 將textString復制到新創(chuàng)建的attrString中。
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), textString);

// 創(chuàng)建一個將作為屬性添加到attrString的顏色。
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };
CGColorRef red = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);

// 將前13個字符的顏色設置為紅色。
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 13),
                                 kCTForegroundColorAttributeName, red);

// 使用屬性字符串創(chuàng)建框架集。
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);

// 創(chuàng)建用于繪制文本的路徑數(shù)組。
NSArray *paths = [self paths];

CFIndex startIndex = 0;

// 在OS X中,使用NSColor而不是UIColor。
#define GREEN_COLOR [UIColor greenColor]
#define YELLOW_COLOR [UIColor yellowColor]
#define BLACK_COLOR [UIColor blackColor]

// 對于路徑數(shù)組中的每個路徑......
for (id object in paths) {
    CGPathRef path = (__bridge CGPathRef)object;

    // 將路徑的背景設置為黃色。
    CGContextSetFillColorWithColor(context, [YELLOW_COLOR CGColor]);

    CGContextAddPath(context, path);
    CGContextFillPath(context);

    CGContextDrawPath(context, kCGPathStroke);

    // 為此路徑創(chuàng)建frame并繪制文本。
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
                                     CFRangeMake(startIndex, 0), path, NULL);
    CTFrameDraw(frame, context);

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

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