一般的なテキストレイアウト操作
この章では、いくつかの一般的なテキストレイアウト操作を説明し、Core Text を使用してそれらを実行する方法を示します。この章では、以下のコードリストの操作について説明します。
段落のレイアウト
タイプセットの最も一般的な操作の 1 つは、任意の大きさの長方形領域内に複数行の段落をレイアウトすることです。Core Text はこの操作を簡単にし、数行の Core Text 固有のコードしか必要としません。段落をレイアウトするには、そこへ描画するグラフィックスコンテキスト、テキストがレイアウトされる領域を提供する長方形のパス、および属性付き文字列が必要です。この例のほとんどのコードは、コンテキスト、パス、および文字列を作成して初期化する必要があります。これが行われた後、Core Text はレイアウトを行うのに 3 行のコードしか必要としません。
リスト 2-1 のコードは、段落のレイアウト方法を示しています。このコードは、UIView サブクラス(OS X の NSView サブクラス) の drawRect: メソッドに存在するかもしれません。
リスト 2-1 単純な段落のタイプセット
// 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);
単純なテキストラベル
別の一般的なタイプセット操作では、ユーザーインターフェイス要素のラベルとして使用する 1 行のテキストを描画します。Core Text では、CFAttributedString を使用して行のオブジェクトを作成するコードと、グラフィックコンテキストに行を描画するコードの 2 行のコードしか必要ありません。
リスト 2-2 は、UIView または NSView サブクラスの drawRect: メソッドでこれがどのように行われるかを示しています。このリストでは、プレーンテキスト文字列、フォント、及びグラフィックスコンテキストの初期化を省略し、これらの操作はこの文書の他のリストで示します。属性辞書を作成し、それを使用して属性付き文字列を作成する方法を示しています。(フォントの作成は、フォント記述子の作成 および フォント記述子からのフォントの作成 を参照してください。)
リスト 2-2 単純なテキストラベルのタイプセット
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);
列のレイアウト
複数の列にテキストを配置するのは、別の一般的なタイプセット操作です。厳密に言えば、Core Text 自体は、一度に 1 つの列(コラム) のみをレイアウトし、列のサイズや位置は計算しません。Core Text を呼び出して計算したパス領域内にテキストをレイアウトする前に、これらの操作を行なって下さい。このサンプルでは、Core Text は、各列にテキストをレイアウトするだけでなく、各列のテキスト文字列内に部分範囲も提供します。
リスト 2-3 の createColumnsWithColumnCount: メソッドは、描画する列の数をパラメータとして受け取り、パスの配列を、各列 1 つずつのパスとして返します。
リスト 2-4 には、このリストの最初に定義されているローカルの createColumnsWithColumnCount メソッドを呼び出す drawRect: メソッドの実装が含まれています。このコードは UIView サブクラス(OS X では NSView サブクラス) にあります。サブクラスには attributedString プロパティが含まれていますが、このプロパティはここには表示されませんが、このリスト内で呼び出され、レイアウトされる属性付き文字列を返すアクセサです。
リスト 2-3ビューを列に分割する
- (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; }
リスト 2-4列のテキストレイアウトを実行する
// 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); }
手動改行
Core Text では、特別なハイフン化処理やそれに類する要件がない限り、通常は改行を手動で行う必要はありません。フレームセッターは、改行を自動的に実行します。また、Core Text を使用すると、テキストの各行を改行する場所を正確に指定できます。リスト 2-5 は、フレームセッターで使用されるオブジェクトである、タイプセッターを作成し、タイプセッターを使用して適切な改行を見つけ、タイプセット行を手動で作成する方法を示しています。このサンプルでは、描画する前に行を中央に配置する方法も示しています。
このコードは、UIView サブクラス(OS X では NSView サブクラス) の drawRect: メソッド内にあるかもしれません。リストには、コードで使用される変数の初期化は表示していません。
リスト 2-5 手動改行の実行
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;
段落スタイルの適用
リスト 2-6 は、属性付き文字列に段落スタイルを適用する関数を実装しています。この関数は、フォント名、ポイントサイズ、及びテキスト行間のスペース量を増減する行間をパラメータとして受け取ります。この関数は リスト 2-7 のコードで呼び出され、これはプレーンテキスト文字列を作成し、applyParaStyle 関数を使用して指定された段落属性を持つ属性付き文字列を作成し、フレームセッターとフレームを作成してフレームを描画します。
リスト 2-6 段落スタイルの適用
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; }
リスト 2-7 では、スタイル設定された文字列を使用してフレームセッターを作成します。コードはフレームセッターを使用してフレームを作成し、フレームを描画します。
リスト 2-7 スタイル付きの段落を描画する
- (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 では、リスト 2-7 のコメントで示されているように、グラフィックスコンテキストを違ったものとして取得し、座標を反転しません。
長方形でない領域にテキストを表示する
長方形でない領域にテキストを表示するのは、長方形以外のパスを記述することです。リスト 2-8 の AddSquashedDonutPath 関数は、ドーナツ型のパスを返します。パスを取得したら、通常の Core Text 関数を単に呼び出して属性を適用し、描画します。
リスト 2-8 長方形でない領域にテキストを表示
// 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); }
前の章 次の章