目次 | 前の項目 | 次の項目 | Java 2D API |
TextLayout クラスは、アラビア語やヘブライ語などのさまざまな書記法の複数の字体と文字を含むテキストをサポートしています。(アラビア語とヘブライ語の場合、許容できる程度の表示を行うにはテキストの形状決定と順序付けをやり直す必要があるので、表示は特に困難です。)TextLayout を使うと、英語だけのテキストを対象とする場合でも、テキストの表示と寸法決定のプロセスは単純化されます。TextLayout を使えば、手間をかけることなく高品質の文字体裁を実現できます。
TextLayout クラスは、ユーザに代わってグリフの配置と順序付けを管理します。TextLayout を使うと、次のことができます。
場合によっては、テキストレイアウトを自分で計算し、使用するグリフとグリフを配置する場所を正確に制御したいことがあります。グリフのサイズ、カーニングテーブル、および合字に関する情報を使えば、テキストレイアウトを計算するための独自のアルゴリズムを構築し、システムのレイアウト機構を省略できます。詳細は、「独自のテキストレイアウト機構の実装」を参照してください。
TextLayout は、双方向 (BIDI) テキストも含め、正しい形状と順序でテキストを自動的にレイアウトします。1 行のテキストを表すグリフの形状決定と順序付けを適切に行うためには、TextLayout はテキストの完全なコンテキストについて知る必要があります。
テキストの基準方向は、通常、テキストの属性 (スタイル) によって設定されます。この属性がない場合、TextLayout は Unicode の双方向アルゴリズムに従って、段落内の最初のいくつかの文字から基準方向を導き出します。
TextLayout は、キャレットの形状、位置、および角度など、キャレットに関する情報を保持しています。この情報を使うと、単方向テキストと双方向テキストのどちらでも、キャレットを簡単に表示できます。双方向テキストでキャレットを描画する場合は、TextLayout を使うと、キャレットが適切な位置に置かれることが保証されます。TextLayout は、デフォルトのキャレット Shape を提供しており、デュアルキャレットを自動的にサポートします。TextLayout は、イタリック体と斜体のグリフに対しては、図 4-12 に示すような角度付きのキャレットを作成します。 これらのキャレット位置は、強調表示とヒット判定ではグリフ間の境界としても使われ、ユーザに違和感を与えないようになっています。
挿入ポイントが与えられると、getCaretShapes メソッドは 2 つの要素からなる配列 Shape を返します。このうち要素 0 には強いキャレットが、要素 1 には弱いキャレット (存在する場合) が含まれています。デュアルキャレットは、これらの Shape を両方とも描画するだけで表示できます。 キャレットは、自動的に適切な位置にレンダリングされます。独自のキャレット Shape を使う場合は、TextLayout からキャレットの位置と角度を取り出して自分でキャレットを描画することができます。
次の例は、デフォルトの強いキャレットと弱いキャレットの Shape を、色を変えて表示します。これは、デュアルキャレットを区別するための一般的な方法です。
Shape[] caretShapes = layout.getCaretShapes(hit); g2.setColor(PRIMARY_CARET_COLOR); g2.draw(caretShapes[0]); if (caretShapes[1] != null){ g2.setColor(SECONDARY_CARET_COLOR); g2.draw(caretShapes[1]); }
TextLayout を使って、ユーザが左矢印キーまたは右矢印キーを押したときに挿入オフセットを決定することもできます。現在の挿入オフセットを表す TextHitInfo オブジェクトを与えると、getNextRightHit メソッドは、右矢印キーが押された場合の正しい挿入オフセットを表す TextHitInfo オブジェクトを返します。 getNextLeftHit メソッドは、左矢印キーについて同じ情報を返します。次の例では、右矢印キーに反応して現在の挿入オフセットが移動します。
TextHitInfo newInsertionOffset = layout.getNextRightHit(insertionOffset); if (newInsertionOffset != null) { Shape[] caretShapes = layout.getCaretShapes(newInsertionOffset); // draw carets ... insertionOffset = newInsertionOffset; }
TextLayout は、テキストのヒット判定を行うための簡単な機構を提供しています。hitTestChar メソッドは、マウスからの x 座標と y 座標を引数に取り、TextHitInfo オブジェクトを返します。TextHitInfo には、指定された位置の挿入オフセットと、ヒットがあったのはどちら側かが含まれています。 挿入オフセットは、ヒットにもっとも近いオフセットです。ヒットが行の終端からはみ出している場合は、行の終端のオフセットが返されます。次の例は、TextLayout に対して hitTestChar を呼び出し、getInsertionIndex を使ってオフセットを取り出します。
TextHitInfo hit = layout.hitTestChar(x, y); int insertIndex = hit.getInsertionIndex();
強調表示領域を表す Shape は TextLayout から取得できます。TextLayout は、強調表示領域の大きさを計算するときに、コンテキストを自動的に考慮します。 TextLayout は、論理的強調表示と視覚的強調表示の両方をサポートしています。次の例は、強調表示領域を強調表示色で塗りつぶし、塗りつぶした領域の上に TextLayout を描画します。これは、強調表示テキストを表示するための 1 つの簡単な方法です。
Shape highlightRegion = layout.getLogicalHighlightShape(hit1, hit2); graphics.setColor(HIGHLIGHT_COLOR); graphics.fill(highlightRegion); graphics.drawString(layout, 0, 0);
TextLayout では、このオブジェクトが表しているテキストのすべての範囲のグラフィカルメトリックスにアクセスできます。TextLayout から取得できるメトリックスには、アセント、ディセント、レディング、有効幅、可視有効幅、および境界の矩形領域があります。1 つの TextLayout には、複数の Font を関連付けることができ、異なる字体に対して異なるフォントを使用できます。TextLayout のアセントとディセントの値は、その TextLayout で使われているすべてのフォントを通じての最大値です。TextLayout のレディングの計算はより複雑で、単にレディングの最大値ではありません。
TextLayout の有効幅とは、長さのことです。つまり、一番左のグリフの左端から一番右のグリフの右端までの距離です。有効幅は、「有効幅の合計」と呼ばれることもあります。 「可視有効幅」は、後続の空白を含まない TextLayout の長さです。
TextLayout のバウンディングボックスは、レイアウト内のすべてのテキストを囲みます。これには、すべての可視グリフおよびキャレットの境界が含まれます。(これらのうち一部のものは、起点または起点に有効幅を加えたものからはみ出ることがあります。)バウンディングボックスは、画面上の特定の位置に対するものではなく、TextLayout の起点に対する相対的なものです。
次の例は、TextLayout のテキストを、レイアウトのバウンディングボックスの中に描画します。
graphics.drawString(layout, 0, 0); Rectangle2D bounds = layout.getBounds(); graphics.drawRect(bounds.getX()-1, bounds.getY()-1, bounds.getWidth()+2, bounds.getHeight()+2);
TextLayout を使って、複数の行にまたがるテキストを表示することもできます。たとえば、1 つの段落を対象に、ある一定の幅で行を折り返して複数行のテキストとして表示することができます。この場合、テキストの各行を表す TextLayouts を直接作成することはしません。 これらは、ユーザに代わって LineBreakMeasurer が生成してくれます。双方向テキストでは、段落内のすべてのテキストを把握するまでは順序付けを適切に行うことができない場合があります。 LineBreakMeasurer は、コンテキストに関する十分な情報をカプセル化するので、正しい TextLayout を生成できます。
複数の行にまたがってテキストを表示する場合、行の長さは一般に表示領域の幅によって決まります。行ブレーク (行の折り返し) は、行を収めなければならないグラフィカルな幅に基づいて、行の開始場所と終了場所を決定するプロセスです。
もっとも一般的な方法は、各行に収まる範囲で、できるだけ多くの単語を配置する方法です。この方法は、LineBreakMeasurer に実装されています。このほかに、より複雑な行ブレークの方法として、ハイフネーションを使う、段落内部での行の長さをできるだけ揃えるなどの方法があります。 Java 2DTM API は、これらの方法の実装は提供していません。
テキストの段落を複数の行に分割するには、段落全体に対して LineBreakMeasurer を構築し、次に nextLayout を呼び出してテキストを順次処理し、行ごとに TextLayout を生成します。
LineBreakMeasurer は、この処理を行うために、テキスト内でのオフセットを保持しています。最初は、オフセットはテキストの先頭にあります。このオフセットは、nextLayout を呼び出すたびに、生成された TextLayout の文字カウントの分だけ移動します。 オフセットがテキストの末尾に到達すると、nextLayout は null を返します。
LineBreakMeasurer が生成する各 TextLayout の可視有効幅は、指定された行の幅を超えることはありません。nextLayout を呼び出すときに、指定する幅を変えると、固定位置にイメージがある HTML ページや、タブで区切られたフィールドなど、複雑な領域にテキストを分割して収めることができます。 BreakIterator を渡して、有効な分割点がどこかを LineBreakMeasurer に指示することもできます。 BreakIterator を渡さなかった場合は、デフォルトローケルの BreakIterator が使われます。
次の例では、2 か国語のテキストが 1 行ずつ描画されます。各行は、基準方向が左から右か右から左かに応じて、左マージンまたは右マージンのどちらかに揃えられます。
Point2D pen = initialPosition; LineBreakMeasurer measurer = new LineBreakMeasurer(styledText, myBreakIterator); while (true) { TextLayout layout = measurer.nextLayout(wrappingWidth); if (layout == null) break; pen.y += layout.getAscent(); float dx = 0; if (layout.isLeftToRight()) dx = wrappingWidth - layout.getAdvance(); layout.draw(graphics, pen.x + dx, pen.y); pen.y += layout.getDescent() + layout.getLeading(); }