中級
Text Layout Framework(TLF)は、「高度なテキスト編集およびレイアウト機能」を提供します。「ActionScript 3.0から見るFlash Professional CS5」でご紹介したTLFテキストを使えば、その機能をこれまでのテキストフィールド(TextFieldクラス)と同じ感覚で手軽に利用できます。
Text Layout Frameworkというのは、「ActionScript 3.0 で記述された独立したコンポーネントライブラリです」(「新しい Text Layout Framework の特徴」)[*1]。「TLF ライブラリには、10 個のパッケージに編成された約 100 個の ActionScript 3.0 クラスおよびインターフェイスが含まれています」(「Text Layout Framework の構造」)。このTLFライブラリを使えば、テキストをさらにきめ細かく扱うことができます。
ただ、Text Layout Frameworkを使いこなすには、文字どおりその枠組み(framework)を理解しなければなりません。テキストの構造とフォーマットやその表示、さらに編集操作などについて、各クラスが分担・連携して高度な機能を実現しているからです。
[*1]「Text Layout Framework は Flash Player に組み込まれていません。これはすべて ActionScript 3.0 で記述された独立したコンポーネントライブラリです」(前出「新しい Text Layout Framework の特徴」)。そのため、書出されたSWFファイルは、Flash Player 10 (Flash CS4 Professional対応)でも基本的に動きます。
テキストコンテンツは、TextFlowオブジェクトに構造化して納めます。このテキストコンテンツは「ストーリー」と呼ばれます。Text Layout Frameworkにおけるテキストの構造化については、XHTMLが参考にされました。たとえば、"Hello, World"という文字列の単純なテキストコンテンツを例に採りましょう。
TextFlowオブジェクトは、ルート要素です(表001)。そのインスタンスには、段落(p)要素となるParagraphElementオブジェクトを含めることができます。けれど、ParagraphElementインスタンスには、直にテキストや画像は加えられません。テキストをプロパティにもつのは、SpanElementオブジェクトです。よって、テキストをSpanElementインスタンスに設定して、ParagraphElementインスタンスに加えます。
したがって、XMLで表すなら、つぎのようなテキストコンテンツをTextFlowインスタンスとしてつくります(なお、後述04「Text Layout Framework Markup形式のXMLからTextFlowオブジェクトをつくる」参照)。
<TextFlow><p><span>Hello, World</span></p></TextFlow>
表001■単純なテキストコンテンツを構成するために用いられるクラス
| クラス | 相当するXHTML要素 | 子として加えることのできるクラス |
|---|---|---|
| TextFlow | ルート(html) | ParagraphElement / DivElement |
| ParagraphElement | p | SpanElement / InlineGraphicElement / LinkElement / TCYElement |
| SpanElement | span | なし |
要素(エレメント)のインスタンスは、ActionScript 3.0の原則どおりコンストラクタメソッドを呼出して生成します。親のエレメントに子として要素を加えるには、FlowGroupElement.addChild()メソッドを用います。DisplayObjectContainer.addChild()とメソッド名は同じものの、テキストコンテンツに引数の要素(FlowElement型)を加えるだけで、そのままではタイムラインには表示されません。
以下のフレームアクションは、"Hello, World"という文字列が含まれた単純なテキストコンテンツをTextFlowインスタンスに構造化して作成します。なお、TextFlowインスタンスを簡単に確かめるために、FlowGroupElement.getText()メソッドでコンテンツのテキストを[出力]しました。
// フレームアクション
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SpanElement;
var myFlow:TextFlow = new TextFlow(); // <TextFlow></TextFlow>
var paragraph:ParagraphElement = new ParagraphElement(); // <p></p>
var span:SpanElement = new SpanElement(); // <span></span>
span.text = "Hello, World"; // <span>Hello, World</span>
paragraph.addChild(span); // <p><span>Hello, World</span></p>
myFlow.addChild(paragraph); // <TextFlow><p><span>Hello, World</span></p></TextFlow>
trace(myFlow.getText()); // 出力: Hello, World
そのためのメソッドがTextFlowTextLineFactory.createTextLines です。このメソッドの使い方には、ふたつ注意があります。第1に、参照するTextFlowTextLineFactoryインスタンスには、予めテキストの矩形領域をTextLineFactoryBase.compositionBoundsプロパティにRectangleオブジェクトで設定しておかなければなりません。
第2に、このメソッドでTextLineオブジェクトがつくられると、コールバック関数が呼ばれ、でき上がったTextLineインスタンスはその引数として渡されます[*2]。ですから、呼出すコールバック関数を定義する必要があります。
TextFlowTextLineFactory.createTextLines メソッドは、つぎのようにコールバック関数とTextFlowオブジェクトのふたつを引数に渡して呼出します。
TextFlowTextLineFactoryオブジェクト.createTextLines コールバック関数, TextFlowオブジェクト ;
function コールバック関数 TextLineオブジェクト:TextLine :void {
// TextLineオブジェクトの処理
}
Text Layout Frameworkにより単純なテキスト"Hello, World"のストーリーを動的に生成し、タイムラインに配置するフレームアクションが、以下のスクリプト001です。[ムービープレビュー]を確かめると、ステージの左上隅に"Hello, World"というテキストが表れます。
// フレームアクション
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SpanElement;
import flashx.textLayout.factory.TextFlowTextLineFactory;
import flash.geom.Rectangle;
import flash.text.engine.TextLine;
// TextFlowオブジェクトの生成と構造化
var myFlow:TextFlow = new TextFlow();
var paragraph:ParagraphElement = new ParagraphElement();
var span:SpanElement = new SpanElement();
span.text = "Hello, World";
paragraph.addChild(span);
myFlow.addChild(paragraph);
// TextFlowオブジェクトからTextLineオブジェクトを生成
var factory:TextFlowTextLineFactory = new TextFlowTextLineFactory();
factory.compositionBounds = new Rectangle(0, 0, 100, 25);
factory.createTextLines(onCreateTextLines, myFlow);
// コールバック関数: 生成されたTextLineオブジェクトの処理
function onCreateTextLines(myTextLine:TextLine):void {
addChild(myTextLine);
}
[*2] [ActionScript 3.0リファレンスガイド]における[TextFlowTextLineFactory]クラスの「createTextLines()メソッド」の項には、コールバック「関数はTextLineオブジェクトに渡されて、行を表示する役割を担います」と説明しています。しかし英語の原文は、"function is passed the TextLine object and is responsible for displaying the line"です。したがって、文の前半は「関数はTextLineオブジェクトを受取って」と訳すべきでしょう。
テキストコンテンツにフォーマットを適用してみましょう。使うクラスは前出「ActionScript 3.0から見るFlash Professional CS5」でもご紹介したTextLayoutFormatです。フォーマットをストーリー全体のデフォルトにするときは、TextLayoutFormatオブジェクトをTextFlow.hostFormatプロパティに設定します。すると、テキストコンテンツ全体のデフォルトフォーマットが設定されます(図002)。
// フレームアクションに追加
import flashx.textLayout.formats.TextLayoutFormat;
var my_fmt:TextLayoutFormat = new TextLayoutFormat();
my_fmt.fontFamily = "Impact";
my_fmt.fontSize = 24;
myFlow.hostFormat = my_fmt;
図002■テキストコンテンツにフォントファミリーとフォントサイズが適用された
特定の要素にフォーマットを適用することもできます。エレメントのクラスはFlowElementクラスを継承しますので、そのFlowElement.formatプロパティにTextLayoutFormatオブジェクトを設定します[*3]。CSSと同じく、子の要素に指定していないプロパティについては、親要素のフォーマットがデフォルトとして適用されます。
以下のフレームアクション スクリプト002 は、テキストコンテンツをふたつのSpanElementインスタンスに分けました。そして、ストーリー全体にデフォルトフォーマットを設定するとともに、ひとつのSpanElementインスタンスには別のフォーマットを適用しています。
// フレームアクション
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SpanElement;
import flashx.textLayout.factory.TextFlowTextLineFactory;
import flash.geom.Rectangle;
import flash.text.engine.TextLine;
import flashx.textLayout.formats.TextLayoutFormat;
var myFlow:TextFlow = new TextFlow();
var paragraph:ParagraphElement = new ParagraphElement();
var span:SpanElement = new SpanElement(); // ふたつのSpanElementインスタンスに
var span2:SpanElement = new SpanElement(); // テキストコンテンツを分ける
span.text = "Hello, ";
span2.text = "World"
paragraph.addChild(span);
paragraph.addChild(span2);
myFlow.addChild(paragraph);
var factory:TextFlowTextLineFactory = new TextFlowTextLineFactory();
factory.compositionBounds = new Rectangle(0, 0, 240, 25);
// ストーリー全体のデフォルトフォーマットを設定
var my_fmt:TextLayoutFormat = new TextLayoutFormat();
my_fmt.fontFamily = "Impact";
my_fmt.fontSize = 24;
myFlow.hostFormat = my_fmt;
// 特定の要素にフォーマットを適用
var my2_fmt:TextLayoutFormat = new TextLayoutFormat();
my2_fmt.fontFamily = "Helvetica";
my2_fmt.color = 0x0000FF;
span2.format = my2_fmt;
// TextFlowオブジェクトからTextLineオブジェクトを生成
factory.createTextLines(onCreateTextLines, myFlow);
function onCreateTextLines(myTextLine:TextLine):void {
addChild(myTextLine);
}
ひとつ注意しておきたいのは、TextFlowTextLineFactory.createTextLines()メソッドは、テキストコンテンツにフォーマットなどをすべて設定し終えてから呼出さなければならないということです。
[ムービープレビュー]を確かめると、ストーリー全体にはデフォルトとしてImpactフォントの24ポイントが設定され、テキストの一単語("World")にHelveticaの青(0x0000FF)が適用されます。後者でとくに指定していないフォントサイズは、デフォルトの24ポイントのままです(図003)。
図003■テキスト全体のデフォルトと一単語へのフォーマットをそれぞれ設定
なお、ある要素のフォーマットをもとにして新たなTextLayoutFormatインスタンスをつくりたいときは、TextLayoutFormat()コンストラクタの引数にその要素のFlowElement.formatプロパティを渡して呼出します。
var my_fmt:TextLayoutFormat = new TextLayoutFormat(span.format);
ストーリーの要素となる各クラスが継承するFlowElementクラスは、TextLayoutFormatクラスとほぼ同じプロパティをもっています[*4]。ですから、TextLayoutFormatオブジェクトを使うことなく、各エレメントのインスタンスに直接プロパティを設定することもできます。
var span:SpanElement = new SpanElement();
span.text = "Hello, World";
span.fontFamily = "Impact";
span.fontSize = 24;
ただ、複雑な機能を各クラスで分担して扱えるのが、Text Layout Frameworkの大きな特徴です。構造化したテキストコンテンツとそのフォーマットとは分けた方が、一般に管理・応用がしやすいでしょう。
[*3] TextFlowクラスもFlowElementクラスを継承するので、FlowElement.formatプロパティが使えます。TextFlow.hostFormatとともに設定した場合、こちらのプロパティの方が上位の指定と扱われます。すなわち、下位のFlowElement.formatプロパティが優先されます。もっとも、特別な理由がないかぎり、紛らわしくなるので、TextFlow.hostFormatプロパティに絞った方がよいでしょう。
[*4] クラスFlowElementとTextLayoutFormatは、ともにインタフェースITextLayoutFormatを実装するからです。
XMLデータからTextFlowオブジェクトをつくることもできます。その場合マークアップは、Text Layout Framework Markup形式で行います。そして、XMLデータをTextFlowオブジェクトに変換するのが、静的メソッドTextConverter.importToFlow()です。
TextConverter.importToFlow()メソッドには、ソースデータとデータ形式のふたつの引数が必要です。第1引数は、XMLデータであればXMLオブジェクトで渡せます。第2引数のデータの形式は、3つのTextConverter定数の中からひとつを指定します(表002)[*5]。Text Layout Framework Markup形式は、定数TextConverter.TEXT_LAYOUT_FORMATになります。
TextConverter.importToFlow(ソースデータ, データ形式)
| 定数 | データ形式 | 説明 |
|---|---|---|
| TEXT_FIELD_HTML_FORMAT | 単純なHTMLテキスト | TextFieldクラスと基本的に同じタグと属性のサブセットをサポート(具体的なタグは[ヘルプ]参照) |
| PLAIN_TEXT_FORMAT | プレーンテキスト | フォーマットされていない文字列 |
| TEXT_LAYOUT_FORMAT | Text Layout Framework Markup | 「Text Layout Framework Markup」参照 |
前掲スクリプト001と同じTextFlowオブジェクトをつくるには、つぎのようなText Layout Framework Markup形式のXMLをTextConverter.importToFlow()メソッドに渡します。ルート(TextFlow)要素にはXML名前空間(xmlns属性)を宣言することにご注意ください[*6]。
import flashx.textLayout.conversion.TextConverter;
var markup:XML = <TextFlow xmlns='http://ns.adobe.com/textLayout/2008'><p><span>Hello, World</span></p></TextFlow>;
var myFlow:TextFlow = TextConverter.importToFlow(markup, TextConverter.TEXT_LAYOUT_FORMAT);
なお、XMLデータは文字列で指定しても、TextConverter.importToFlow()メソッドで同じようにTextFlowオブジェクトが生成できます。
var markup:String = "<TextFlow xmlns='http://ns.adobe.com/textLayout/2008'><p><span>Hello, World</span></p></TextFlow>";
var myFlow:TextFlow = TextConverter.importToFlow(markup, TextConverter.TEXT_LAYOUT_FORMAT);
[*5]「Text Layout Framework のモデル」の「テキストの読み込み」の項をご参照ください。ただし、掲載されているスクリプトの例では、定数TextConverter.TEXT_FIELD_HTML_FORMATが誤って「TextConverter.HTML_FORMAT」と記述されています。
[*6] XML名前空間の宣言がないと、TextConverter.importToFlow()メソッドはnullを返すようです。前出注[*5]の「テキストの読み込み」、および「フローコンポーザーを使用したコンテンツの表示」に掲載されている例には、XML名前空間が記述されていません。
TextFlowオブジェクトからTextLineインスタンスをつくってタイムラインに表示するには、フローコンポーザーを使う手法もあります。フローコンポーザーの利点は、[ヘルプ]の「フローコンポーザーを使用したコンテンツの表示」につぎのように説明されています[*7]。
フローコンポーザーは、TextFlowTextLineFactory よりもテキストの表示をより詳細に制御する場合に使用します。例えば、フローコンポーザーを使用すると、ユーザーはテキストを選択および編集できます。
フローコンポーザーを使うには、基本として3つのメソッドを呼出します。第1は、ContainerControllerクラスのコンストラクタメソッドで、インスタンスを生成します。引数は3つで、ひとつ目が、できあがったTextLineオブジェクトを加えて表示するSpriteインスタンスです(このインスタンスをコンテナと呼びます)。引数のふたつ目と3つ目は、テキスト領域の幅と高さです(デフォルトは100×100ピクセル)。
new ContainerController(コンテナのSpriteオブジェクト, テキストの幅, テキストの高さ)
第2に、TextFlow.flowComposerプロパティの参照に対してIFlowComposer.addController()メソッドを呼出し、コンテナのSpriteインスタンスとTextFlowオブジェクトを結びつけます。その橋渡しとなるContainerControllerインスタンスを、メソッドの引数に渡します。メソッドに戻り値はありません。
TextFlowオブジェクト.flowComposer.addController(ContainerControllerオブジェクト)
第3に、IFlowComposer.updateAllControllers()メソッドの呼出しにより、テキストコンテンツを再構成し、表示も更新します[*8]。TextLineオブジェクトがつくり直され、コンテナのSpriteインスタンスにおける子インスタンスと置換わります。コンテンツが更新できたら、メソッドはtrueを返します。
TextFlowオブジェクト.flowComposer.updateAllControllers()
以上3つのメソッドの呼出しによりTextFlowオブジェクトからTextLineインスタンスをつくり、タイムラインに配置するフレームアクションはつぎのとおりです。
import flashx.textLayout.container.ContainerController;
var controller:ContainerController = new ContainerController(this, 240, 25);
myFlow.flowComposer.addController(controller);
myFlow.flowComposer.updateAllControllers();
TextFlowオブジェクトとコンテナのSpriteおよびContainerControllerインスタンスの関係は、[ヘルプ]の「フローコンポーザーを使用したコンテンツの表示」には図004のように示されています。
図004■TextFlowオブジェクトとコンテナのSpriteおよびContainerControllerインスタンスの関係
TEXT LAYOUT FRAMEWORK MARKUP形式のXMLデータからTextFlowオブジェクトをつくり、フローコンポーザーによりテキストコンテンツを表示したのが、つぎのフレームアクション(スクリプト004)です。
// フレームアクション
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.conversion.TextConverter;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.compose.IFlowComposer;
// 変数宣言と初期値設定
var markup:XML = <TextFlow xmlns='http://ns.adobe.com/textLayout/2008'><p><span>Hello, World</span></p></TextFlow>;
var myFlow:TextFlow = TextConverter.importToFlow(markup, TextConverter.TEXT_LAYOUT_FORMAT);
var my_fmt:TextLayoutFormat = new TextLayoutFormat();
var controller:ContainerController = new ContainerController(this, 240, 25);
var composer:IFlowComposer = myFlow.flowComposer; // IFlowComposerの参照
// デフォルトフォーマット設定
my_fmt.fontFamily = "Impact";
my_fmt.fontSize = 24;
myFlow.hostFormat = my_fmt;
// フローコンポーザーによるコンテンツ更新
composer.addController(controller);
composer.updateAllControllers());
上記スクリプトでは、TextFlow.flowComposerプロパティの参照を、後で扱いやすいようにIFlowComposer型の変数にとりました。もっとも、実際に得られる参照はインタフェースIFlowComposerを実装したStandardFlowComposerクラスのオブジェクトです[*9]。
[ムービープレビュー]を確かめると、再掲図002と同じテキストコンテンツがステージ左上隅に表示されます。
図002■テキストコンテンツにフォントファミリーとフォントサイズが適用された(再掲)
[*7] 他方、TextFlowTextLineFactoryは、単純でメモリをくいません。そのため、ユーザーインタラクションのない静的テキストにふさわしいでしょう(「TextFlowTextLineFactory を使用したコンテンツの表示」参照)。
[*8] 「フローコンポーザーを使用したコンテンツの表示」に掲載されている例には、「updateAllContainers()」という記述があります。しかし、このようなメソッドはありません。「updateAllControllers()」メソッドの誤りでしょう。
[*9] したがって、呼出すふたつのメソッドも、StandardFlowComposer.addController()およびStandardFlowComposer.updateAllControllers()になります。
なお、今のところインタフェースIFlowComposerを実装するクラスは、StandardFlowComposerしかありません。しかし、「今後、IFlowComposer も実装する新しいクラスがフレームワークに追加される可能性があります」(前出[ヘルプ]「フローコンポーザーを使用したコンテンツの表示」)。
TEXT LAYOUT FRAMEWORKを使ったごく基本的な処理と仕組みについてご紹介してきました。TextFieldクラスはもちろん、TLFTextFieldクラスと比べても、扱いは単純ではありませんでした。その代わりに、きめ細かな設定や複雑な処理ができるのです。
今回解説したクラスの機能は、大きく3つに分けられます。
第1は、構造化したテキストコンテンツ(ストーリー)の生成です。ふたつの方法がありました。ひとつは、flashx.textLayout.elementsパッケージのクラスを使って、要素のインスタンスを入れ子にしていくことです。クラスとしては、TextFlowを始め、ParagraphElementやSpanElementなどを用います。もうひとつは、TextConverter.importToFlow()メソッドで、テキストコンテンツのデータをストーリーに変換するやり方です。とくに構造化が表現しやすいTEXT LAYOUT FRAMEWORK MARKUPを試しました。
第2は、テキストコンテンツにフォーマットを設定することです。これは、TextLayoutFormatクラスを使うことがお勧めでした。
そして第3は、テキストコンテンツをどのようにステージに表示するかです。DisplayObjectのサブクラスTextLineのインスタンスを生成してコンテナに配置します。わかりやすいのは、TextFlowTextLineFactory.createTextLines()メソッドでTextLineのインスタンスをつくることです。もっと細かな制御が求められるときは、フローコンポーザーでTextFlowオブジェクトとコンテナを関連づけて扱うことができました。
このように機能を分けるのは、大きくふたつの意図があります。ひとつは、クラスを専門特化させることにより、機能を増やしたり、応用の範囲を広げることができます。
もうひとつは、互いの独立性を高めて、余分な干渉・影響をなくすことです。前述第1の文書の構造と第2のフォーマットを分けるというのは、CSSの考え方と同じです。さらにFlashでは、Webページがブラウザに委ねている、画面にどう表示するかという前述第3の機能も必要になります。
本稿で説明したTEXT LAYOUT FRAMEWORKの処理やクラスはごく一部です。詳しくは、[ヘルプ]の「Text Layout Framework の使用」をお読みいただくとよいでしょう。ただ、この解説には「Model-View-Controller(MVC)デザインパターン」という開発技法の専門用語が出てきます[*10]。その内容を知らない場合には、クラスの役割分担を考える定石のひとつというくらいに理解しておけばよいでしょう。
3つに分けた機能のうちModelには、前述第1の構造化したテキストコンテンツ生成と第2のフォーマットが含まれています。つぎに、Viewは文字どおり前述第3のテキストコンテンツの表示です。そして、Controllerに当たるクラスは、本稿では説明していません。ユーザーインタラクションに関わる機能を担います。
デザインパターンの知識がない場合には、MVCがそれぞれ何かというより、[ヘルプ]の中で説明されているクラスや処理がどのような機能を果たすのか理解するよう努めればよいでしょう。そうすることで、逆にMVCの役割分担が想像できるようになるからです。
[*10] 内容については、たとえばWikipedia「Model View Controller」が参考になります。