Core Text 编程指南02:常见文本布局操作

本章介绍了一些常见的文本布局操作,并说明了如何使用Core Text执行它们。本章包含以下代码清单操作:

  • 列出一个段落
  • 简单的文字标签
  • 列式布局
  • 手动换行
  • 应用段落样式
  • 在非矩形区域中显示文本

列出一个段落

排版中最常见的操作之一是在任意大小的矩形区域内布置多线段。 Core Text使此操作变得简单,只需要几行特定于Core Text的代码。要布置段落,您需要绘制图形上下文,矩形路径以提供布置文本的区域以及属性字符串。此示例中的大多数代码都需要创建和初始化上下文,路径和字符串。完成此操作后,Core Text只需要三行代码即可完成布局。

清单2-1中的代码显示了段落的布局方式。此代码可以驻留在UIView子类的drawRect:方法中(OS X中的NSView子类)。

清单2-1排版一个简单的段落(下面是苹果文档给出的例子)

// 在iOS中初始化图形上下文。
CGContextRef context = UIGraphicsGetCurrentContext();
// 翻转上下文坐标,仅限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);

// 创建一个限定要绘制文本的区域的路径。
// 路径不需要一定是矩形。
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.");

// 创建一个最大长度为0的可变属性字符串。
// 最大长度是关于要保留多少内部存储空间的提示。
// 0表示没有提示。
CFMutableAttributedStringRef attrString =
     
CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

// 将textString复制到新创建的attrString中
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),
     textString);

// 创建一个将作为属性添加到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);

// 使用属性字符串创建frame集合。
CTFramesetterRef framesetter =
     
CTFramesetterCreateWithAttributedString(attrString);
CFRelease(attrString);

// 创建一个frame。
CTFrameRef frame = 
CTFramesetterCreateFrame(framesetter,
      CFRangeMake(0, 0), path, NULL);

// 在给定的上下文中绘制指定的帧。
CTFrameDraw(frame, context);

// 释放我们使用的对象。
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);

下面是我的demo里面的相关代码(swift语言):

override func draw(_ rect: CGRect) {
    super.draw(rect)
    // 在iOS中初始化图形上下文。
    let context = UIGraphicsGetCurrentContext()
    // 翻转上下文坐标,仅限iOS。
    context?.translateBy(x: 0, y: self.bounds.size.height)
    context?.scaleBy(x: 1.0, y: -1.0)
    // 设置文本矩阵。
    context?.textMatrix = .identity
    // 创建一个限定要绘制文本的区域的路径。
    // 路径不需要一定是矩形。
    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
    // 创建一个最大长度为0的可变属性字符串。
    // 最大长度是关于要保留多少内部存储空间的提示。
    // 0表示没有提示。
    let attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)
    // 将textString复制到新创建的attrString中
    CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), textString)
    // 创建一个将作为属性添加到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)
    // 使用属性字符串创建frame集合。
    let framesetter = CTFramesetterCreateWithAttributedString(attrString!)
    // 创建一个frame
    let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)
    // 在给定的上下文中绘制指定的帧。
    CTFrameDraw(frame, context!)
}

简单的文字标签

另一种常见的排版操作是绘制单行文本以用作用户界面元素的标签。在Core Text中,这只需要两行代码:一行用于创建具有CFAttributedString的行对象,另一行用于将行绘制到图形上下文中。

清单2-2显示了如何在UIViewNSView子类的drawRect: 方法中完成此操作。该列表省略了纯文本字符串,字体和图形上下文的初始化,本文档中其他列表中显示的操作。它显示了如何创建属性字典并使用它来创建属性字符串。 (字体创建显示在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?>类型数据创建
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本身一次只列出一列,不计算列大小或位置。您在调用Core Text之前执行这些操作,以在您计算的路径区域内布置文本。在此示例中,Core Text除了在每列中布置文本外,还为每列提供了文本字符串中的子范围。

清单2-3中的createColumnsWithColumnCount:方法接受要绘制的列数作为参数,并返回路径数组,每列一个路径。

清单2-4包含drawRect: 方法的实现,该方法调用本清单中首先定义的本地createColumnsWithColumnCount方法。此代码驻留在UIView子类(OS X中的NSView子类)中。子类包含一个attributesString属性,此属性未在此处显示,但在此列表中调用其访问者以返回要布局的属性字符串。

清单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);
}

// 创建一个布局路径数组,每列一个。
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执行列式文本布局

// Override drawRect: 将属性字符串绘制到列中。
// (在OS X中,NSView的drawRect:方法采用NSRect参数,
//  但该列表中未使用该参数。)
- (void)drawRect:(CGRect)rect{
 // 在iOS中初始化图形上下文。
CGContextRef context = UIGraphicsGetCurrentContext();

// 仅在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);

// 使用属性字符串创建框架集。
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(
                                  (CFAttributedStringRef)self.attributedString);

// 调用createColumnsWithColumnCount函数来创建一个数组
// 三条路径(列)。
CFArrayRef columnPaths = [self createColumnsWithColumnCount:3];

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

// 为每列(路径)创建一个框架。
for (column = 0; column < pathCount; column++) {
    //获取此列的路径。
    CGPathRef path = (CGPathRef)CFArrayGetValueAtIndex(columnPaths, column);

    //为此列创建一个框架并绘制它。
    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自动执行换行。或者,Core Text使你可以准确指定每行文本的中断位置。清单2-5显示了如何创建排版工具,框架集使用的对象,以及直接使用排版工具查找适当的换行符并手动创建排版行。此示例还显示了在绘制之前如何使线居中。

此代码可以位于UIView子类的drawRect: 方法中(OS X中的NSView子类)。该列表未显示代码中使用的变量的初始化。

清单2-5执行手动换行

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

// 使用属性字符串创建排字机。
CTTypesetterRef typesetter = 
CTTypesetterCreateWithAttributedString(attrString);

// 找到从字符串开头到给定宽度的行的中断。
CFIndex start = 0;
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);


// 使用返回的字符数(到中断)来创建该行。
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):

// 执行手动换行
override func draw(_ rect: CGRect) {
    super.draw(rect)
    // 在iOS中初始化图形上下文。
    let context = UIGraphicsGetCurrentContext()
    // 翻转上下文坐标,仅限iOS。
    context?.translateBy(x: 0, y: self.bounds.size.height)
    context?.scaleBy(x: 1.0, y: -1.0)
    // 设置文本矩阵。
    context?.textMatrix = .identity
    // 创建一个限定要绘制文本的区域的路径。
    // 路径不需要一定是矩形。
    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
    // 创建一个最大长度为0的可变属性字符串。
    // 最大长度是关于要保留多少内部存储空间的提示。
    // 0表示没有提示。
    let attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)
    // 将textString复制到新创建的attrString中
    CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), textString)
    // 创建一个将作为属性添加到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)
    // 使用属性字符串创建排字机。
    let typesetter = CTTypesetterCreateWithAttributedString(attrString!)
    // 找到从字符串开头到给定宽度的行的中断。
    var start: CFIndex = 10
    let count: CFIndex = CTTypesetterSuggestLineBreak(typesetter, start, width)
    // 使用返回的字符数(到中断)来创建该行。
    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?>类型数据创建
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实现了一个将段落样式应用于属性字符串的函数。该函数接受字体名称,磅值和行间距作为参数,这增加或减少文本行之间的空间量。此函数由清单2-7中的代码调用,该代码创建纯文本字符串,使用applyParaStyle函数创建具有给定段落属性的属性字符串,然后创建frame集和frame,并绘制frame

清单2-6应用段落样式

NSAttributedString* applyParaStyle(
            CFStringRef fontName , CGFloat pointSize,
            NSString *plainText, CGFloat lineSpaceInc){
// 创建字体,以便我们确定其高度。
CTFontRef font = CTFontCreateWithName(fontName, pointSize, NULL);

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

// 创建段落样式设置。
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);

// 将段落样式应用于字符串以创建属性字符串。
NSAttributedString* attrString = [[NSAttributedString alloc]
                           initWithString:(NSString*)plainText
                           attributes:attributes];

return attrString;
}

在清单2-7中,样式化字符串用于创建框架集。代码使用框架集创建框架并绘制框架。

清单2-7绘制样式段落

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

// 仅在iOS中翻转上下文坐标。
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);

// 创建一个填充视图的路径。
CGPathRef path = CGPathCreateWithRect(rect, NULL);

// 创建一个绘制框架。
CTFrameRef frame = CTFramesetterCreateFrame(
                                framesetter, CFRangeMake(0, 0), path, NULL);

// 绘制frame。
CTFrameDraw(frame, context);
CFRelease(frame);
CGPathRelease(path);
CFRelease(framesetter);
}

OS X中,NSView drawRect:方法接收NSRect参数,但CGPathCreateWithRect 函数需要CGRect参数。因此,您必须使用以下函数调用将NSRect对象转换为CGRect对象:

CGRect myRect = NSRectToCGRect([self bounds]);

此外,在OS X中,您可以获得不同的图形上下文,并且不会翻转其坐标,如清单2-7中的注释所示。

在非矩形区域中显示文本

在非矩形区域中显示文本的困难部分是描述非矩形路径。清单2-8中的AddSquashedDonutPath函数返回一个环形路径。获得路径后,只需调用常用的Core Text函数即可应用属性并绘制。

清单2-8在非矩形路径中显示文本

// 创建一个甜甜圈形状的路径。
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调用之外的路径,以便仅计算路径一次。

- (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中翻转上下文坐标。
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.");

// 创建一个可变的属性字符串。
 CFMutableAttributedStringRef attrString =
            CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

// 将textString复制到新创建的attrString中。
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), textString);

// 创建一个将作为属性添加到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);

// 使用属性字符串创建框架集。
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);

// 创建用于绘制文本的路径数组。
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]

// 对于路径数组中的每个路径......
for (id object in paths) {
    CGPathRef path = (__bridge CGPathRef)object;

    // 将路径的背景设置为黄色。
    CGContextSetFillColorWithColor(context, [YELLOW_COLOR CGColor]);

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

    CGContextDrawPath(context, kCGPathStroke);

    // 为此路径创建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);
}
上一章 目录 下一章
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容