版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.10.22 |
前言
Core Text
框架主要用来做文字处理,是的iOS3.2+
和OSX10.5+
中的文本引擎,让您精细的控制文本布局和格式。它位于在UIKit
中和CoreGraphics/Quartz
之间的最佳点。接下来这几篇我们就主要解析该框架。感兴趣的可以前面几篇。
1. Core Text框架详细解析(一) —— 基本概览
2. Core Text框架详细解析(二) —— 关于Core Text
3. Core Text框架详细解析(三) —— Core Text总体概览
字体布局操作
本章介绍一些常见的文本布局操作,并显示如何使用Core Text执行它们。 本章包含以下代码列表操作:
- Laying Out a Paragraph
- Simple Text Label
- Columnar Layout
- Manual Line Breaking
- Applying a Paragraph Style
- Displaying Text in a Nonrectangular Region
Laying Out a Paragraph - 布局一个段落
排版typesetting
中最常见的操作之一是在任意大小的矩形区域内布置多行段落。 Core Text
使此操作变得容易,只需要几行Core Text特定的代码。 要布置该段落,您需要绘制图形上下文,一个矩形路径来提供文本布局的区域,以及一个属性字符串。 此示例中的大多数代码是创建和初始化上下文,路径和字符串。 完成之后,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 - 简单文本标签
另一个常见的排版typesetting
操作是绘制一行文本以用作用户界面Label的文本。 在Core Text中,这只需要两行代码:一个用CFAttributedString
创建行对象,另一个用于将该行绘制到图形上下文中。
Listing 2-2 显示了如何在UIView或NSView子类的drawRect:方法中完成此操作。 该列表省略了在本文档中其他列表中显示的纯文本字符串,字体、图形上下文和操作的初始化。 它显示如何创建属性字典并使用它来创建属性字符串。 (字体创建显示在 Creating Font Descriptors 和 Creating 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本身一次只列出一列,不计算列大小或位置。您可以在调用Core Text之前进行这些操作,以便在您计算的路径区域中布置文本。在此示例中,Core Text除了在每列中布置文本之外,还为每列提供了文本字符串中的子范围。
Listing 2-3 中的createColumnsWithColumnCount:
方法接受要绘制的列数的参数,并返回一个路径数组,每列的一个路径。
Listing 2-4 包括drawRect:方法的一个实现,它调用本列表中首先定义的本地createColumnsWithColumnCount
方法。该代码驻留在UIView子类(OS X中的NSView子类)中。该子类包含一个attributString
属性,此属性在此处未显示,但在此列表中调用其访问器以返回要排列的属性字符串。
// 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
会自动执行换行。 或者,Core Text使您能够准确地指定每行文本中断的位置。 Listing 2-5显示了如何创建一个排版机typesetter
,一个由framesetter使用的对象,并直接使用排版机来找到适当的换行符并手动创建一个排版行。 此示例还显示了绘制之前如何居中的一行。
该代码可以在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 - 应用段落样式
Listing 2-6实现了一个将段落样式应用于属性符串的函数。 该函数接受字体名称,点大小和增加或减少文本行之间的空格量的行间距的参数。 该函数由Listing 2-7中的代码调用,它创建一个纯文本字符串,使用applyParaStyle
函数创建一个带有给定段落属性的属性字符串,然后创建一个framesetter
和frame
,并绘制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
字符串用于创建一个framesetter
,代码用framesetter
创建一个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参数,但CGPathCreateWithRect函数需要一个CGRect参数。 因此,必须使用以下函数调用将NSRect对象转换为CGRect对象:
CGRect myRect = NSRectToCGRect([self bounds]);
此外,在OS X中,您可以不同地获取图形上下文,并且不会翻转其坐标,如Listing 2-7中的注释所示。
Displaying Text in a Nonrectangular Region - 在非矩形区域中显示文本
在非矩形区域中显示文本的困难部分是描述非矩形路径。 Listing 2-8 中的AddSquashedDonutPath
函数返回一个环形路径。 一旦你有路径,只需调用通常的Core Text函数来应用属性和绘制。
// 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);
}
后记
未完,待续~~~