大重美幸の「これ見落としてませんか? ActionScript 3.0」

第7回 TextBlockクラスで多彩な文字表現

テキストを自由にアニメーションさせたい、もっと美しい文字でテキストを表示したいと思ったとき、テキストフィールドでは難しい場合があります。その悩みを解決するのがFlash CS4から搭載された新テキストエンジンです。今回は新テキストエンジンを使うためのTextBlockクラスにチャレンジです!

サンプルファイル : edge_oshige_07_sample_fla.zip (47KB)

新テキストエンジンを使う

TextBlockクラスはFlash CS4(Flash Player 10、AIR 1.5)で新機能として搭載された新テキストエンジンを利用するためのクラスです。CS4次期バージョンでは正式に搭載される予定のText Layout Framework (TLF)も新テキストエンジンを利用するために提供される機能ですが、ActionScriptのコーディングレベルでとらえたときに両者のどちらが手軽なのかは、目的によって判断がわかれます。いずれにせよTLFについてはCS4次期バージョンを待つことにして、TextBlockクラスの使い方と新テキストエンジンの利点を確認してみましょう。

次のサンプルがテキストをステージに表示するだけの簡単な例です。簡単な例と言ってもTextFieldクラスを使った場合とはずいぶん違います。TextFieldクラスの場合は、TextFieldクラスで作ったテキストフィールドにストリングデータを入れるという2つのステップでテキスト表示を行いますが、TextBlockクラスの手順は3ステップあります。

まず、TextBlockクラスでテキストブロック、すなわち容器を作ります。そして、これにTextElementクラスで作ったテキストエレメントを入れます。ここまではテキストフィールドの場合と同じですが、ここからが違います。テキストフィールドは表示オブジェクトなのでそのままステージに配置すれば終わりです。しかし、TextBlockクラスで作ったテキストブロックは表示オブジェクトではありません。テキストブロックに入れておいたテキストエレメントをステージに表示するには、テキストブロックから1行分のデータを取り出してテキストラインを作り、それを1行ずつ並べて表示するという手順を繰り返します。まずバケツに水を溜め、その水をひしゃくですくって庭にまくというたとえでわかりますか?(笑)

TextBlockクラスを使ってテキストを表示
fig1 TextBlockクラスを使ってテキストを表示します。禁則処理、均等配置などが適用されています

次のスクリプトでは、まず最初にテキストエレメントtextElementを作成しています。テキストエレメントにはストリングデータとElementFormatクラスで作った表示書式を指定します。日本語を表示する場合には、ElementFormatクラスのlocaleプロパティの値を"ja"にします。


//書式
var format:ElementFormat = new ElementFormat();
format.locale = "ja";

//テキストエレメントを作る
var textElement:TextElement = new TextElement(str, format);

次にTextBlockクラスのtextBlockインスタンスを作り、作っておいたtextElementをcontentプロパティに割り当てます。


//テキストブロックを作る
var textBlock:TextBlock = new TextBlock();
//テキストブロックのコンテンツにテキストエレメントを設定する
textBlock.content = textElement;

テキストブロックtextBlockの準備ができたならば、textBlockからテキストラインを作って変数textLineに入れます。テキストラインはtextBlock.createTextLine()で作りますが、最初のテキストラインを作るには第1引数をnullにし、第2引数に行の幅を指定します。テキストラインは表示オブジェクトなので、座標を指定して表示リストにaddChild()すればステージに表示できます。


//テキストブロックから最初のtextLineを作る
var textLine:TextLine = textBlock.createTextLine(null, block_width);

問題は2行目以降のテキストラインの作り方です。1行目のテキストラインを作ったときのようにtextBlock.createTextLine()を使いますが、第1引数に1行目として作ったtextLineを指定します。すると1行目の残りから次の指定の幅の分のテキストラインが作られます。


//テキストブロックから次のtextLineを作る
textLine = textBlock.createTextLine(textLine, block_width);

これを同じ長さのテープを並べて貼っていくように位置合わせしてステージに貼ります。この操作をwhileステートメントを使って、テキストラインがnullになるまで繰り返せば、テキストブロックのすべてのエレメントを取り出してステージに並べたことになります。 なお、テキストラインの間隔はascentプロパティとdescentプロパティの値に適当な値、ここでは5ピクセルを行間として足しています。ascentプロパティとdescentプロパティの値は、次の図で示すように行のベースラインまでの距離です。

テキストラインのascentプロパティとdescentプロパティの値
fig2 テキストラインのascentプロパティとdescentプロパティの値(ヘルプのTextLineの説明から抜粋)

[sample] sample_TextBlock.fla
TextBlockクラスを使ってテキストを表示します。

var str:String = "まず始めにTextBlockクラスでテキストブロックという入れ物を作り、";
str += "その中にテキストやグラフィックスといったコンテンツを入れておきます。";
str+="次にTextBlockから1行分のデータを取り出してテキストラインを作り、";
str+="そのテキストラインの座標、傾きなどを設定して表示します。";
//書式
var format:ElementFormat = new ElementFormat();
format.locale = "ja";
format.fontSize = 16;
//テキストエレメントを作る
var textElement:TextElement = new TextElement(str, format);
//テキストブロックを作る
var textBlock:TextBlock = new TextBlock();
//テキストブロックのコンテンツにテキストエレメントを設定する
textBlock.content = textElement;
//均等配置設定
textBlock.textJustifier = new EastAsianJustifier();
var block_width:Number = 340;
//テキストブロックから最初のtextLineを作る
var textLine:TextLine = textBlock.createTextLine(null, block_width);
var offset:Point = new Point(25,50);
var tx:Number = 0;
var ty:Number = 0;
//textLineの値がnullになるまで繰り返す。
while (textLine != null){
	textLine.x = tx+offset.x;
	textLine.y = ty+offset.y;
	//表示位置の行送り
	ty +=(textLine.ascent + textLine.descent+5);
	addChild(textLine);
	//テキストブロックから次のtextLineを作る
	textLine = textBlock.createTextLine(textLine, block_width);
}

縦書きで表示する

TextBlockクラスでは日本語の縦書き表示もできます。次の図のように、縦書きでは単純に文字が縦に並ぶだけでなく、句読点や促音などの表示位置が正しく右寄せになり、音引きも縦棒に変わります。半角の英数字は横に回転した向きになります。文字を回転させるにはテキストブロックのlineRotationプロパティをTextRotation.ROTATE_90に設定します。


//回転方向(縦書き)
textBlock.lineRotation = TextRotation.ROTATE_90;

その他の基本的な考え方は先の横書きの場合と同じです。違う点と言えば、縦書きの場合は行が右から左へと並ぶので、テキストラインのx座標の値を指定して行送りする点でしょう。

縦書き
fig3  TextBlockクラスを使ってテキストを縦書きで表示します

[sample] sample_ROTATE_90.fla
lineRotationプロパティをTextRotation.ROTATE_90にすると縦書きになります。

var str:String = "まず始めにTextBlockクラスでテキストブロックという入れ物を作り、";
str += "その中にテキストやグラフィックスといったコンテンツを入れておきます。";
str+="次にTextBlockから1行分のデータを取り出してテキストラインを作り、";
str+="そのテキストラインの座標、傾きなどを設定して表示します。";
//書式
var format:ElementFormat = new ElementFormat();
format.locale = "ja";
format.fontSize = 16;
//テキストエレメントを作る
var textElement:TextElement = new TextElement(str, format);
//テキストブロックを作る
var textBlock:TextBlock = new TextBlock();
//テキストブロックのコンテンツにテキストエレメントを設定する
textBlock.content = textElement;
//均等配置設定
textBlock.textJustifier = new EastAsianJustifier();
//回転方向(縦書き)
textBlock.lineRotation = TextRotation.ROTATE_90;
var block_width:Number = 200;
//テキストブロックから最初のtextLineを作る
var textLine:TextLine = textBlock.createTextLine(null, block_width);
var offset:Point = new Point(320,50);
var tx:Number = 0;
var ty:Number = 0;
//textLineの値がnullになるまで繰り返す。
while (textLine != null){
	textLine.x = tx+offset.x;
	textLine.y = ty+offset.y;
	//表示位置の行送り(縦書きなので右から左へ進む)
	tx -=(textLine.ascent + textLine.descent+5);
	addChild(textLine);
	//テキストブロックから次のtextLineを作る
	textLine = textBlock.createTextLine(textLine, block_width);
}

複数の領域に横書きのテキストを流し込む

次のサンプルは2つの領域にテキストを流し込んでいる例です。スクリプトは少し複雑になりますが、基本的な考え方はこれまでと少しも変わりません。fillTextLineRect関数を定義してテキストラインの表示を行っていますが、ここでのポイントは、指定の領域に何行のテキストラインが入るかを先に計算して繰り返し回数を決めているところです。そして、1番目の領域が終わったならば、その時点でのテキストラインを2番目の引数として渡して続きのテキストラインを抜き出しています。1番目と2番目の領域は縦横サイズが違うので引数ではその情報も必要です。


//四角形の領域に横書きでテキストラインを書き出す
var remainTextLine:TextLine=fillTextLineRect(sp1,textBlock,null);
//残りのテキストを2個目の四角形領域に書き出す
remainTextLine=fillTextLineRect(sp2,textBlock,remainTextLine);

注意点としてはテキストブロックからテキストエレメントを抜き出したときに、それがいつnullになるかはわからないという点です。テキストエレメントを作っても、それがnullになった時点で処理を抜けるようにします。
なお、このサンプルではfillTextLineRect()の第1引数で指定するスプライトで領域を示すと同時に、それぞれをテキストラインを追加するコンテナとしても利用しています。したがって、コンテナであるスプライトの座標や変形によってテキストの表示も変化します。その場合、領域の計算が狂ってしまうので、スプライトの変形はテキストラインを追加した後から行わなければなりません。アニメーションについては最後の例で示します。

スプライトの領域にテキストを流し込む
fig4 2つのスプライトの領域にテキストを流し込むように表示します

[sample] rectangle_TextLine.fla
2つの領域にテキストを流し込んで表示します。

var str:String = "まず始めにTextBlockクラスでテキストブロックという入れ物を作り、";
str += "その中にテキストやグラフィックスといったコンテンツを入れておきます。";
str+="次にTextBlockから1行分のデータを取り出してテキストラインを作り、";
str+="そのテキストラインの座標、傾きなどを設定して表示します。";
//テキストラインを表示したい四角形の領域を示すスプライトを作る
var sp1:Sprite = new Sprite();
sp1.graphics.beginFill(0xDDDDDD);
sp1.graphics.drawRect(0,0,180,120);
sp1.graphics.endFill();
sp1.x=30;
sp1.y=30;
addChild(sp1);
//2個目の四角形領域
var sp2:Sprite = new Sprite();
sp2.graphics.beginFill(0xFFCDCD);
sp2.graphics.drawRect(0,0,150,240);
sp2.graphics.endFill();
sp2.x=220;
sp2.y=30;
addChild(sp2);

//書式
var format:ElementFormat = new ElementFormat();
format.locale="ja";
format.fontSize=16;
//テキストエレメントを作る
var textElement:TextElement = new TextElement(str, format);
//テキストブロックを作る
var textBlock:TextBlock = new TextBlock();
//均等配置設定
textBlock.textJustifier = new EastAsianJustifier();

//テキストブロックのコンテンツにテキストエレメントを入れる
textBlock.content=textElement;
//y=0を合わせる位置
textBlock.baselineZero = TextBaseline.ASCENT;
//四角形の領域に横書きでテキストラインを書き出す
var remainTextLine:TextLine=fillTextLineRect(sp1,textBlock,null);
//残りのテキストを2個目の四角形領域に書き出す
remainTextLine=fillTextLineRect(sp2,textBlock,remainTextLine);

function fillTextLineRect(sp:Sprite,tb:TextBlock, textLine:TextLine):TextLine {
	//四角形の領域
	var rect:Rectangle=sp.getRect(stage)
	var i:int=0;
	if (textLine==null) {
		i=1;
		//テキストブロックの最初の行のとき
		var textLine:TextLine=tb.createTextLine(null,sp.width);
		sp.addChild(textLine);
	}
	//残りのテキストラインを指定領域に入るだけ表示する
	var gap:Number = 8;
	var h:Number=textLine.ascent+textLine.descent+gap;
	var numOfLines:int=Math.floor(sp.height/h);
	for (i=Math.max(0,i); i<numOfLines; i++) {
		//次のテキストラインを作る
		textLine=tb.createTextLine(textLine,rect.width);
		if (textLine==null) {
			//取り出せるデータが終わったので処理を終了する
			return null;
		}
		//行送り
		textLine.y=textLine.ascent+h*i;
		sp.addChild(textLine);
	}
	return textLine;
}

円形領域への縦書きとアニメーション

最後の例は複数の円形の領域に縦書きのテキストを流し込んでいます。円形の領域にテキストを流し込むように表示するというのもTextBlockクラスらしい使い方ですが、ここでもう1つ注目して欲しいのは、テキストラインを追加しているスプライトを回転、拡大縮小、アルファの変化とアニメーションさせているところです。何ということのないアニメーションに見えますが、このような変形を行ってもテキストを表示できるという点がTextBlockクラスを利用する大きなメリットの1つです。なお、座標計算の問題から、このスクリプトではテキストラインのコンテナとして使うスプライトの変形を、テキストラインを追加したあとから行う必要があります。

縦書き、複数領域へのテキストの流し込みについては先のサンプルとほとんど変わりません。このサンプルが難しく見える部分は円形領域にテキストを合わせるところですが、それも実際には中学レベルの算数です。円形領域に何行のテキストラインが並ぶかを計算して行間のx座標を調べ、そのx座標に対応する円周上のy座標を計算して行の長さを求めています。

さて、今回はここまでです。次回はさらにTextBlockクラスの機能について掘り下げてみましょう。


テキストを円形領域に流し込み、回転、拡大縮小、アルファ変化のアニメーションを行います

[sample] circle_TextLine.fla
テキストを円形領域に流し込み、アニメーションを行います。

var str:String = "まず始めにTextBlockクラスでテキストブロックという入れ物を作り、";
str += "その中にテキストやグラフィックスといったコンテンツを入れておきます。";
str+="次にTextBlockから1行分のデータを取り出してテキストラインを作り、";
str+="そのテキストラインの座標、傾きなどを設定して表示します。";
//テキストラインを表示したい円形の領域を示すスプライトを作る
var sp1:Sprite = new Sprite();
sp1.name="sp1";
sp1.graphics.beginFill(0xFFFFCD);
sp1.graphics.drawCircle(0,0,100);
sp1.graphics.endFill();
sp1.x=280;
sp1.y=130;
addChildAt(sp1,0);
//2個目の円形領域
var sp2:Sprite = new Sprite();
sp2.name="sp2";
sp2.graphics.beginFill(0xFF99CC);
sp2.graphics.drawCircle(0,0,70);
sp2.graphics.endFill();
sp2.x=100;
sp2.y=180;
addChildAt(sp2,0);

//書式
var format:ElementFormat = new ElementFormat();
format.locale="ja";
format.fontSize=14;
//テキストエレメントを作る
var textElement:TextElement = new TextElement(str, format);
//テキストブロックを作る
var textBlock:TextBlock = new TextBlock();
//テキストブロックのコンテンツにテキストエレメントを入れる
textBlock.content=textElement;
//均等配置設定
textBlock.textJustifier = new EastAsianJustifier();
//回転方向(縦書き)
textBlock.lineRotation = TextRotation.ROTATE_90;
//x=0を合わせる位置
textBlock.baselineZero = TextBaseline.ASCENT;
//円形領域に縦書きでテキストラインを書き出す
var remainTextLine:TextLine=fillTextLineCircle(sp1,textBlock,null);
//残りのテキストを2個目の円形領域に書き出す
remainTextLine=fillTextLineCircle(sp2,textBlock,remainTextLine);
//円を拡大しながら回転させる
sp1.addEventListener(Event.ENTER_FRAME, entrerFrameHandler);
sp2.addEventListener(Event.ENTER_FRAME, entrerFrameHandler);

function fillTextLineCircle(sp:Sprite,tb:TextBlock, textLine:TextLine):TextLine {
	//円の半径
	var r:Number = sp.width/2;
	var i:int=0;
	var gap:Number = 8;
	var dw:Number;
	if (textLine==null) {
		i=2;
		var fontSize:Number=tb.baselineFontSize
		var h0:Number = Math.sqrt(r*r-Math.pow(r-fontSize,2))*2;
		//テキストブロックの最初の行のとき
		var textLine:TextLine=tb.createTextLine(null,h0);
		//座標設定
		dw = textLine.ascent+textLine.descent+gap;
		textLine.x=r-dw;
		textLine.y=-Math.sqrt(r*r-Math.pow(r-dw,2));
		sp.addChild(textLine);
	}
	//残りのテキストラインを指定領域に入るだけ表示する
	 dw=textLine.ascent+textLine.descent+gap;
	var numOfLines:int=Math.floor(2*r/dw);
	for (i=Math.max(1,i); i<numOfLines; i++) {
		//次のテキストラインを作る
		var h:Number = Math.sqrt(r*r-Math.pow(r-dw*i,2))*2;
		textLine=tb.createTextLine(textLine,h);
		if (textLine==null) {
			//取り出せるデータが終わったので処理を終了する
			return null;
		}
		//座標設定
		textLine.x=r-dw*i;
		textLine.y=-Math.sqrt(r*r-Math.pow(r-dw*i,2));
		sp.addChild(textLine);
	}
	return textLine;
}
//回転、拡大縮小、アルファ変更のアニメーション
function entrerFrameHandler(event:Event):void{
	var sp:Sprite = event.target as Sprite;
	var ratio:Number = Math.sin(sp.rotation*Math.PI/180);
	sp.scaleX = sp.scaleY = 1+ratio/3;
	sp.alpha = 1+ratio;
	if(sp.name=="sp1"){
		sp.rotation += 2;
	}else{
		sp.rotation -= 2;
	}
}

 

関連情報


大重美幸氏写真大重美幸
(おおしげよしゆき)

日立情報システムズ、コミュニケーションシステム研究所を経て独立。株式会社ロクナナ顧問。執筆、講師、ソフトウェア開発を行う。趣味はサーフィンとジョギング。茅ヶ崎在住。著著は約50冊。
twitter : @oshige@as3note

近著:Adobe Flash CS4 詳細!ActionScript 3.0入門ノート[完全改訂版]ActionScript3.0辞典[FlashPlayer10/9対応] ブラウザで無料ではじめるActionScript 3.0 - it's a wonderfl world