目次 | 前へ | 次へ

第 4 章

フォントとテキストレイアウト

テキスト文字列では、Java 2D™ API の変形および描画メカニズムを使用できます。また、Java 2D API は、細かなフォント制御と洗練されたテキストレイアウトをサポートするテキスト関連クラスを提供します。これには、機能の強化された Font クラスと新しい TextLayout クラスが含まれます。

この章では、java.awt および java.awt.font のインタフェースおよびクラスを介してサポートされる、新しいフォントおよびテキストレイアウト機能に焦点を当てて説明します。これらの機能の使用法についての詳細は、Java チュートリアルの「Working with Text APIs」レッスンを参照してください。

テキスト分析と国際化については、java.text のドキュメントと Java チュートリアルの「Writing Global Programs」を参照してください。Swing に実装されているテキストレイアウトメカニズムの使用法についての詳細は、java.awt.swing.text のドキュメントと Java チュートリアルの「Creating a GUI with JFC/Swing」を参照してください。

:この章に含まれている国際テキストレイアウトに関する情報は、『International Text in JDK 1.2』 (Mark Davis、Doug Felt、John Raley 共著、copyright 1997、Taligent, Inc.) という論文に基づいています。

4.1 インタフェースとクラス

次の表は、フォントおよびテキストレイアウト関連の主なインタフェースとクラスです。これらのインタフェースとクラスのほとんどは、java.awt.font パッケージに含まれています。Font など、いくつかのインタフェースとクラスは、以前のバージョンの JDK との下位互換性を維持するために、java.awt パッケージの一部になっています。

インタフェース
説明
MultipleMaster
Type 1 Multiple Master フォントを表します。マルチプルマスターデザインコントロールにアクセスできるようにするために、マルチプルマスターフォントである Font オブジェクトによって実装されます。
OpenType
Open Type フォントと True Type フォントを表します。フォントの sfnt テーブルにアクセスできるようにするために、Open Type フォントまたは True Type フォントの Font オブジェクトによって実装されます。
クラス
説明
Font
(java.awt)
ホストシステム上で使用可能なフォントフェースのコレクションからフォントフェースのインスタンスを表します。詳細なフォント情報を特定できるようにし、フォントとそのグリフに関する情報にアクセスできるようにします。
FontRenderContext
テキストを正確に測定するのに必要な情報をカプセル化します。
GlyphJustificationInfo
ウェイト、プライオリティー、アブソーブ、リミットなど、グリフの位置揃えプロパティーに関する情報を表します。
GlyphMetrics
単一グリフのメトリックスを提供します。
GlyphVector
グリフおよびその位置のコレクションです。
GraphicAttribute
テキスト内に組み込むグラフィックを指定する TextLayout 属性の基底クラスです。ShapesImagesTextLayout に組み込むことを可能にする ShapeGraphicAttributeImageGraphicAttribute によって実装されます。サブクラス化すれば、独自に文字を置き換えるグラフィックを実装できます。
ImageGraphicAttribute
スーパークラス: GraphicAttribute
TextLayout 内に Images を描画するときに使われる GraphicsAttribute です。
LineBreakMeasurer
複数行にまたがるテキストのブロックを、指定された行の長さに収まるように
複数の TextLayout オブジェクトに分割します。
LineMetrics
1 行の文字のレイアウトと一連の行のレイアウトに必要なフォントメトリックスにアクセスできるようにします。これらのメトリックには、アセント、ディセント、レディング、高さ、およびベースライン情報が含まれます。
ShapeGraphicAttribute
スーパークラス: GraphicAttribute
TextLayout 内に Shapes を描画するときに使われる GraphicsAttribute です。
TextAttribute
テキストレンダリングに使われる属性のキーと値を定義します。
TextHitInfo
TextLayout 内の文字のヒット判定情報を表します。
TextLayout
インタフェース: Cloneable
双方向テキストも含め、字体付き文字データの不変のグラフィカル表現を提供します。

4.2 フォントのコンセプト

Font クラスは、詳細なフォント情報の特定と高度な文字体裁機能の使用を可能にするために強化されています。

Font オブジェクトは、システム上で使用できるフォントフェースのコレクションからのフォントフェースのインスタンスを表します。一般的なフォントフェースの例として、Helvetica BoldCourier Bold Italic などがあります。

Font に関連付けられる名前には、論理名、ファミリ名、およびフォントフェース名の 3 つがあります。

  • Font オブジェクトの論理名は、プラットフォーム上で使用可能な具体的なフォントの 1 つにマップされる名前です。論理フォント名は、JDK 1.1 およびそれ以前のリリースで Font を指定するために使われる名前です。Java™ 2 SDK で Font を指定する場合は、論理名ではなくフォントフェース名を使う必要があります。getName を呼び出すと、Font から論理名を取得できます。プラットフォーム上で使用可能な具体的なフォントにマップされた論理名のリストを取得するには、java.awt.Toolkit.getFontList を呼び出してください。
  • Font オブジェクトのファミリ名は、Helvetica など、複数のフェースにまたがって文字体裁のデザインを決定するフォントファミリの名前です。ファミリ名は、getFamily メソッドを通じて取得します。
  • Font オブジェクトのフォントフェース名は、システムにインストールされている実際のフォントを指します。Java 2 SDK でフォントを指定するときは、このフォントフェース名を使う必要があります。フォントフェース名は、単にフォント名と呼ばれることがよくあります。getFontName を呼び出すことによってフォント名を取得できます。システム上で使用可能なフォントフェース名を調べるには、GraphicsEnvironment.getAllFonts を呼び出します。

Font に関する情報には、getAttributes メソッドを通じてアクセスできます。Font の属性には、名前、サイズ、変形、および、ウェイトやポスチャーなどのフォント機能があります。

LineMetrics オブジェクトは、アセント、ディセント、レディングといった、Font に関連付けられた測定情報をカプセル化します。

  • アセントは、ベースラインから上方線までの距離のことです。この距離は一般に大文字の高さを表しますが、文字によっては上方線より上に突き出るものもあります。
  • ディセントとは、ベースラインから下方線までの距離です。ほとんどの文字の最低点はディセント内に収まりますが、文字によっては下方線より下に突き出るものもあります。
  • レディングとは、ディセントの線の下端から次の行の上端までの推奨距離です。

前の文で、このグラフィックスを説明しています。

図 4-1 行メトリックス

これらの情報は、行に沿って文字を適切に配置し、複数の行を互いの位置関係によって配置するために使われます。これらの行メトリックスには、getAscentgetDescent、および getLeading メソッドを通じてアクセスできます。また、LineMetrics を通じて、Font の高さ、ベースライン、下線、および取り消し線に関する情報にアクセスすることもできます。

4.3 テキストレイアウトのコンセプト

テキストを表示するには、適切なグリフと合字を使ってテキストの形状を決定し、配置しなければなりません。このプロセスのことを、テキストレイアウトと呼びます。テキストレイアウトのプロセスには、次のものが含まれます。

  • 適切なグリフと合字を使ったテキストの形状決定。
  • テキストの適切な順序付け。
  • テキストの寸法決定と配置。

テキストをレイアウトするのに使われる情報は、キャレットの配置、ヒット検出、強調表示などのテキスト操作でも必要になります。

国際市場に展開できるソフトウェアを開発するには、適切な書記法の規則に従ってさまざまな言語でテキストをレイアウトしなければなりません。

4.3.1 テキストの形状決定

グリフとは、1 つまたは複数の文字の視覚的な表現です。グリフの形状、サイズ、および位置は、そのグリフが置かれたコンテキストに依存します。フォントとスタイルによっては、単一の文字または複数の文字の組み合わせを表すのに、多くの異なるグリフが使われることがあります。

たとえば、手書きの筆記体によるテキストでは、隣接する文字とどのように結び付くかによって、特定の文字がさまざまな形状を取ることがあります。

一部の書記法、特にアラビア語では、グリフのコンテキストを常に考慮しなければなりません。英語の場合と異なり、アラビア語では筆記体の使用は不可欠であり、筆記体を使用せずにテキストを表示することはできません。

これらの筆記体は、コンテキストによって大幅に形状が変わる可能性があります。たとえば、アラビア文字の heh には、次の図 4-2 に示すように 4 つの筆記体があります。

非接続、右側接続、両側接続、および左側接続の、アラビア語筆記体。

図 4-2 アラビア語の筆記体

これら 4 つの形は、それぞれ非常に異なっています。このように形状が変化していることは、英語の筆記体の場合でも基本的に同じです。

コンテキストによっては、2 つのグリフが形状を大きく変化させ、融合して単一のグリフを形成することもあります。この種の融合されたグリフは、合字と呼ばれます。たとえば、ほとんどの英語フォントには、図 4-3 に示すような合字 fi があります。この融合されたグリフでは、単に 2 つの文字を並べるのではなく、文字 f の突き出た部分を考慮し、次の「i」と並べたときに自然に見えるように、2 つの文字を結合しています。

前の文で、このグラフィックスを説明しています。

図 4-3 英語の合字

合字は、アラビア語でも使われており、一部の合字の使用は不可欠です。つまり、適切な合字を使用せずに、特定の文字の組み合わせを表示することはできません。アラビア文字から形成される合字は、英語の場合よりもさらに形状が大きく変化しています。たとえば、図 4-4 は、となり合った 2 つのアラビア文字が 1 つに組み合わされてどのような合字を形成するかを示したものです。

前の文で、このグラフィックスを説明しています。

図 4-4 アラビア語の合字

4.3.2 テキストの順序付け

Java™ プログラミング言語では、テキストは Unicode 文字エンコーディングを使ってエンコードされます。Unicode 文字エンコーディングを使うテキストは、論理的順序に従ってメモリーに格納されます。論理的順序とは、文字や単語を読み書きする順序のことです。論理的順序は、対応するグリフを表示する順序である視覚的順序と必ずしも同じではありません。

ある特定の書記法 (筆記) でのグリフの視覚的順序は、筆記順序と呼ばれます。たとえば、ローマ字テキストの筆記順序は左から右で、アラビア語とヘブライ語の筆記順序は右から左です。

書記法によっては、筆記順序に加えて、テキスト行にグリフや単語を配列するための規則を持つものがあります。たとえば、アラビア語とヘブライ語では、文字は右から左へと並べられますが、数字は左から右へと並べられます。したがって、英語のテキストが埋め込まれていない場合でも、アラビア語とヘブライ語は本当の意味で双方向言語であると言えます。

書記法の視覚的順序は、複数の言語が混在する場合でも維持しなければなりません。このことを示しているのが、英語の文の中にアラビア語の語句が埋め込まれた図 4-5 です。

注: 次の例とそのあとのいくつかの例では、アラビア語とヘブライ語のテキストを大文字で表し、空白は下線で表しています。各図には、メモリーに格納されている文字の表現 (論理的順序の文字) と、実際に表示される文字の表現 (視覚的順序の文字) の 2 つの部分があります。文字ボックスの下の数字は、挿入オフセットを表します。

前の文で、このグラフィックスを説明しています。

図 4-5 双方向テキスト

アラビア語の単語は英語の文の一部ですが、アラビア語の筆記順序である右から左へと記述されています。イタリック体のアラビア語の単語は、プレーンテキストのアラビア語の単語より論理的にあとにあるので、視覚的にはプレーンテキストのアラビア語の左側にあります。

左から右に記述するテキストと右から左に記述するテキストが混在する行を表示する場合は、基準方向が重要です。基準方向とは、主要な書記法の筆記順序のことです。たとえば、テキストが主に英語で書かれていて、その中にアラビア語がいくつか埋め込まれている場合、基準方向は左から右になります。一方、テキストが主にアラビア語で書かれていて、いくつかの英語や数字が埋め込まれている場合は、基準方向は右から左になります。

通常の方向のテキストの一部を表示するときの順序は、基準方向によって決まります。図 4-5 に示した例では、基準方向は左から右です。この例には 3 つの方向があり、文頭の英語のテキストは左から右へ、アラビア語のテキストは右から左へ、ピリオドは左から右へ記述されています。

テキストの流れの中にグラフィックが埋め込まれることがよくあります。テキストの流れと行の折り返しに与える影響という点では、これらのインライングラフィックは、グリフと同じように動作します。インライングラフィックが文字の流れの中で適切な場所に表示されるようにするには、グリフと同じ双方向レイアウトアルゴリズムを使って、インライングラフィックを配置する必要があります。

1 行中のグリフの順序を決定するために使われる正確なアルゴリズムの詳細は、『The Unicode Standard, Version 2.0』のセクション 3.11「Bidirectional Algorithm」の説明を参照してください。

4.3.3 テキストの寸法決定と配置

モノスペースフォントを使っている場合は別ですが、1 つのフォントでも文字によって幅は異なります。したがって、テキストの配置と寸法決定では、使われている文字数ではなく、どの文字が使われているかを正確に把握する必要があります。たとえば、プロポーショナルフォントで表示される数字の列を右揃えする場合、空白をいくつか追加することによってテキストを配置することはできません。列を適切にそろえるには、各数字の正確な幅を調べ、その幅に応じて適切に調整を行う必要があります。

テキストは、複数のフォントや、ボールド、イタリックなどのさまざまな字体を使って表示されることがあります。この場合は、どのような字体が使われているかによって、同じ文字でも形状や幅が異なる可能性があります。テキストの適切な配置、寸法測定、およびレンダリングを行うには、各文字とその文字に適用される字体の両方を把握する必要があります。TextLayout は、この処理をユーザーに代わって行います。

ヘブライ語やアラビア語などの言語でテキストを適切に表示するには、各文字の寸法を測定し、隣接する文字のコンテキストの中で文字を配置する必要があります。文字の形状と位置はコンテキストによって変わることがあるので、コンテキストを考慮せずにこれらのテキストの寸法決定と配置を行うと、得られる結果は不適切なものになります。

4.3.4 テキスト操作のサポート

表示されているテキストを編集できるようにするには、次のことが可能でなければなりません。

  • ユーザーがテキストを入力したときに、新しい文字が挿入される場所を示すキャレットの表示。
  • ユーザーの入力に応じたキャレットと挿入ポイントの移動。
  • ユーザーの選択部分の検出 (ヒット検出)。
  • 選択されたテキストの強調表示。

4.3.4.1 キャレットの表示

編集可能なテキストでは、現在の挿入ポイントをグラフィカルに表すためにキャレットが使われます。挿入ポイントとは、テキスト内で新しい文字が挿入される位置のことです。通常、キャレットは、2 つのグリフの間の点滅する縦線で表示されます。新しい文字は、このキャレットの場所に挿入され、表示されます。

キャレット位置の計算は、特に双方向テキストの場合には複雑になることがあります。双方向テキストでは、文字オフセットに対応する 2 つのグリフが互いに隣接して表示されるわけではないので、方向の境界上の挿入オフセットは、キャレット位置として 2 つの可能性を持ちます。これを示しているのが図 4-6 です。この図では、キャレットがどのグリフに対応しているかを示すために、キャレットが角括弧で表示されています。

前の文で、このグラフィックスを説明しています。

図 4-6 デュアルキャレット

文字オフセット 8 は、「_」のあと、A の前の場所に対応しています。ここでユーザーがアラビア語の文字を入力すると、入力した文字のグリフは A の右 (前) に表示されます。英語の文字を入力すると、そのグリフは _ の右 (後) に表示されます。

このような状況に対処するために、一部のシステムでは、強い (プライマリ) キャレットと弱い (セカンダリ) キャレットのデュアルキャレットを表示します。強いキャレットは、文字の方向がテキストの基準方向と同じ場合に、挿入された文字が表示される場所を示します。弱いキャレットは、文字の方向が基準方向と逆の場合に、挿入された文字が表示される場所を示します。TextLayout はデュアルキャレットを自動的にサポートしますが、JTextComponent はデュアルキャレットをサポートしていません。

双方向テキストを対象とする場合は、文字オフセットの前にグリフの幅を単純に加えるだけでは、キャレット位置を計算することはできません。このような方法を使用した場合、図 4-7 に示すように、キャレットが間違った場所に描画されてしまいます。

前の文で、このグラフィックスを説明しています。

図 4-7 不正な場所に描画されたキャレット

キャレットを適切に配置するには、オフセットの左側にあるすべてのグリフの幅を追加するとともに、現在のコンテキストを考慮する必要があります。コンテキストを考慮に入れないと、グリフのメトリックスが表示と一致しなくなる可能性があります。どのグリフが使われるかは、コンテキストによっても左右されるからです。

4.3.4.2 キャレットの移動

どのテキストエディタでも、ユーザーは矢印キーを使ってキャレットを移動できます。ユーザーは、自分が押した矢印キーの方向にキャレットが移動することを期待しています。左から右に記述するテキストでは、挿入オフセットの移動も単純です。右矢印キーが押されたら挿入オフセットを 1 つ増やし、左矢印キーが押されたら挿入オフセットを 1 つ減らします。双方向テキストや、合字が含まれたテキストでは、矢印キーを押すと、方向の境界でキャレットがいくつかのグリフを飛び越え、方向が逆になる部分では逆方向にキャレットが移動することになります。

双方向テキストでキャレットを円滑に移動するには、テキストの方向を考慮する必要があります。右矢印キーが押されたときに挿入オフセットを 1 つ増やし、左矢印キーが押されたときに挿入オフセット 1 つ減らすだけでは不十分です。現在の挿入オフセットが、右から左に記述する文字の中にある場合は、右矢印キーが押されたら挿入オフセットを減らし、左矢印キーが押されたら挿入オフセットを増やす必要があります。

方向の境界にまたがったキャレットの移動は、さらに複雑になります。図 4-8 は、ユーザーが矢印キーを使って移動中に、方向の境界を越えるとどのようなことが起こるかを示しています。表示されているテキスト内で右に 3 つ移動すると、それぞれオフセット 7、19、および 18 の文字に移動することになります。

前の文で、このグラフィックスを説明しています。

図 4-8 キャレットの移動

グリフによっては、その間にキャレットを置くことができないものがあります。この場合は、これらのグリフがあたかも 1 つの文字を表しているかのように、キャレットを移動する必要があります。たとえば、o とウムラウトが 2 つの独立した文字によって表されている場合、o とウムラウトの間にキャレットを置くことはできません (詳細は、『The Unicode Standard, Version 2.0』の第 5 章を参照)。

TextLayout は、双方向テキストでのキャレットの円滑な移動を簡単に実現するためのメソッド ((getNextRightHitgetNextLeftHit) を提供しています。

4.3.4.3 ヒット判定

デバイス空間内での場所は、しばしばテキストオフセットに変換しなければなりません。たとえば、選択可能なテキスト上でユーザーがマウスをクリックした場合、マウスの場所はテキストオフセットに変換され、選択範囲の一方の端として使われます。これは、論理的にはキャレットの配置と逆の操作です。

双方向テキストを対象とする場合、ディスプレイ内の視覚的には単一の場所が、元のテキストでは 2 つの異なるオフセットに対応することがあります。このことを示しているのが、図 4-9 です。

前の文で、このグラフィックスを説明しています。

図 4-9 双方向テキストのヒット判定

視覚的には単一の場所が、2 つの異なるオフセットに対応することがあるので、双方向テキストのヒット判定では、目的の場所のグリフが見つかるまでグリフの幅を計算し、該当するグリフが見つかったらその位置を文字オフセットに対応付けるという処理だけでは不十分です。2 つの選択肢のうち適切なものを選ぶには、ヒットがあったのはどちら側かを検出する必要があります。

TextLayout.hitTestChar を使うと、ヒット判定を行うことができます。ヒット情報は TextHitInfo オブジェクトの中にカプセル化され、ヒットがあったのはどちら側かについての情報もその中に含まれています。

4.3.4.4 選択部分の強調表示

選択範囲の文字は、強調表示領域によってグラフィカルに表示されます。強調表示領域では、グリフは反転表示されるか、または異なる背景色の上に表示されます。

双方向テキストの場合は、キャレット同様、強調表示領域も単方向テキストの場合より複雑になります。双方向テキストでは、隣接する範囲の文字でも、表示されたときに強調表示領域が隣接しないことがあります。逆に、強調表示領域が、視覚的に隣接する範囲のグリフを示している場合でも、単一の隣接する範囲の文字に対応するとはかぎりません。

このため、双方向テキストで選択部分を強調表示する場合は、次の 2 つの方法が存在することになります。

  • 論理的強調表示 - 論理的強調表示では、選択された文字はテキストモデルの中で常に隣接する。強調表示領域は隣接していない場合がある。論理的強調表示の例については、図 4-10 を参照。
  • 視覚的強調表示 - 視覚的強調表示では、選択された文字の範囲が複数存在することがあるが、強調表示領域は常に隣接する。視覚的強調表示の例については、図 4-11 を参照。

論理的強調表示 (文字が隣接)

図 4-10 論理的強調表示 (文字が隣接)

視覚的強調表示 (強調表示領域が隣接)

図 4-11 視覚的強調表示 (強調表示領域が隣接)

論理的強調表示の方が実装は容易です。これは、選択された文字がテキスト内で常に隣接するためです。

4.3.5 Java™ アプリケーションでのテキストレイアウトの実行

使用する Java™ API に応じて、テキストレイアウトの制御を必要なだけ使用できます。

  • ひとまとまりのテキストを表示するだけの場合や、編集可能なテキストの制御が必要な場合は、ユーザーに代わってテキストレイアウトを実行する JTextComponent を使用できます。JTextComponent は、ほとんどの国際アプリケーションのニーズに対処できるように設計されており、双方向テキストをサポートしています。JTextComponent についての詳細は、Java チュートリアルの「Creating a GUI with JFC/Swing」を参照してください。
  • 簡単なテキスト文字列を表示する場合は、Graphics2D.drawString を呼び出し、文字列のレイアウトを Java 2D™ に行わせることができます。drawString は、字体付き文字列や双方向テキストを含む文字列のレンダリングにも使用できます。Graphics2D によるテキストのレンダリングについての詳細は、「グラフィックスプリミティブのレンダリング」を参照してください。
  • 独自のテキスト編集ルーチンを実装する場合は、TextLayout を使うと、テキストレイアウト、強調表示、およびヒット検出を管理できます。TextLayout が提供する機能を利用すれば、さまざまなフォント、言語、および双方向テキストが混在するテキスト文字列など、ほとんどの場合に対処できます。TextLayout の使用法についての詳細は、「テキストレイアウトの管理」を参照してください。
  • テキストの形状決定と配置を完全に制御する場合は、Font を使って独自の GlyphVectors を構築し、これらを Graphics2D を通じてレンダリングできます。独自のテキストレイアウトメカニズムの実装方法についての詳細は、「独自のテキストレイアウトメカニズムの実装」を参照してください。

一般に、テキストレイアウト操作をユーザーが行う必要はありません。ほとんどのアプリケーションでは、JTextComponent が、静的で編集可能なテキストを表示するための最良の解決方法です。ただし、JTextComponent は、双方向テキストでのデュアルキャレットや、隣接してない選択部分の表示をサポートしていません。アプリケーションでこれらの機能が必要な場合、または独自のテキスト編集ルーチンを実装する場合は、Java 2D テキストレイアウト API を使用できます。

4.4 テキストレイアウトの管理

TextLayout クラスは、アラビア語やヘブライ語などのさまざまな書記法の複数の字体と文字を含むテキストをサポートしています。(アラビア語とヘブライ語の場合、許容できる程度の表示を行うにはテキストの形状決定と順序付けをやり直す必要があるので、表示は特に困難です。)

TextLayout を使うと、英語だけのテキストを対象とする場合でも、テキストの表示と寸法決定のプロセスは単純化されます。TextLayout を使えば、手間をかけることなく高品質のタイポグラフィを実現できます。

テキストレイアウトのパフォーマンス
TextLayout は、単純な単方向テキストを表示する場合でも、パフォーマンスに大きな影響を与えることがないように設計されています。TextLayout を使ってアラビア語やヘブライ語を表示する場合は、処理のオーバーヘッドはやや増えます。ただし、1 文字あたりマイクロ秒程度のオーバーヘッドであり、通常の描画コードの実行と比べても非常に小さい値にとどまっています。

TextLayout クラスは、ユーザーに代わってグリフの配置と順序付けを管理します。TextLayout を使うと、次のことができます。

  • 単方向および双方向テキストのレイアウト
  • キャレットの表示と移動
  • テキスト上のヒット判定の実行
  • テキストの選択部分の強調表示

場合によっては、テキストレイアウトを自分で計算し、使用するグリフとグリフを配置する場所を正確に制御したいことがあります。グリフのサイズ、カーニングテーブル、および合字に関する情報を使えば、テキストレイアウトを計算するための独自のアルゴリズムを構築し、システムのレイアウトメカニズムをバイパスできます。詳細は、「独自のテキストレイアウトメカニズムの実装」を参照してください。

4.4.1 テキストのレイアウト

TextLayout は、双方向 (BIDI) テキストも含め、正しい形状と順序でテキストを自動的にレイアウトします。1 行のテキストを表すグリフの形状決定と順序付けを適切に行うためには、TextLayout はテキストの完全なコンテキストについて知る必要があります。

  • ボタン用の 1 語のラベル、ダイアログボックス内の行など、単一の行にテキストが収まる場合は、テキストから直接 TextLayout を構築できます。
  • 単一の行にテキストが収まらない場合、または単一の行をタブで区切った複数の部分に分割する場合は、直接 TextLayout を構築することはできません。この場合は、十分なコンテキストを提供するために LineBreakMeasurer を使う必要があります。

テキストの基準方向は、通常、テキストの属性 (スタイル) によって設定されます。この属性がない場合、TextLayout は Unicode の双方向アルゴリズムに従って、段落内の最初のいくつかの文字から基準方向を導き出します。

4.4.2 デュアルキャレットの表示

TextLayout は、キャレットの Shape、位置、および角度など、キャレットに関する情報を保持しています。この情報を使うと、単方向テキストと双方向テキストのどちらでも、キャレットを簡単に表示できます。双方向テキストでキャレットを描画する場合は、TextLayout を使うと、キャレットが適切な位置に置かれることが保証されます。

TextLayout は、デフォルトのキャレット Shapes を提供しており、デュアルキャレットを自動的にサポートします。TextLayout は、イタリック体と斜体のグリフに対しては、図 4-12 に示すような角度付きのキャレットを作成します。これらのキャレット位置は、強調表示とヒット判定ではグリフ間の境界としても使われ、ユーザーに違和感を与えないようになっています。

前の文で、このグラフィックスを説明しています。

図 4-12 角度付きのキャレット

挿入オフセットが与えられると、getCaretShapes メソッドは 2 つの要素からなる Shapes の配列を返します。このうち要素 0 には強いキャレットが、要素 1 には弱いキャレット (存在する場合) が含まれています。デュアルキャレットは、これらの Shapes を両方ともレンダリングするだけで表示できます。キャレットは、自動的に適切な位置にレンダリングされます。

独自のキャレット Shapes を使う場合は、TextLayout からキャレットの位置と角度を取り出して自分でキャレットを描画できます。

次の例は、デフォルトの強いキャレットと弱いキャレットの Shapes を、色を変えて描画します。これは、デュアルキャレットを区別するための一般的な方法です。

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]);  
} 

4.4.3 キャレットの移動

TextLayout を使って、ユーザーが左矢印キーまたは右矢印キーを押したときに挿入オフセットを決定することもできます。現在の挿入オフセットを表す TextHitInfo オブジェクトを指定すると、getNextRightHit メソッドは、右矢印キーが押された場合の正しい挿入オフセットを表す TextHitInfo オブジェクトを返します。getNextLeftHit メソッドは、左矢印キーについて同じ情報を返します。

次の例では、右矢印キーに反応して現在の挿入オフセットが移動します。

TextHitInfo newInsertionOffset =             layout.getNextRightHit(insertionOffset);  
if (newInsertionOffset != null) {  
  Shape[] caretShapes =    
          layout.getCaretShapes(newInsertionOffset); 
  // draw carets 
  ... 
  insertionOffset = newInsertionOffset; 
} 

4.4.4 ヒット判定

TextLayout は、テキストのヒット判定を行うための簡単なメカニズムを提供しています。hitTestChar メソッドは、マウスからの x 座標と y 座標を引数に取り、TextHitInfo オブジェクトを返します。TextHitInfo には、指定された位置の挿入オフセットと、ヒットがあったのはどちら側かが含まれています。挿入オフセットは、ヒットにもっとも近いオフセットです。ヒットが行の終端からはみ出している場合は、行の終端のオフセットが返されます。

次の例は、TextLayout に対して hitTestChar を呼び出し、getInsertIndex を使ってオフセットを取り出します。

TextHitInfo hit = layout.hitTestChar(x, y); 
int insertIndex = hit.getInsertIndex(); 

4.4.5 選択部分の強調表示

強調表示領域を表す ShapeTextLayout から取得できます。TextLayout は、強調表示領域の大きさを計算するときに、コンテキストを自動的に考慮します。TextLayout は、論理的強調表示と視覚的強調表示の両方をサポートしています。

次の例は、強調表示領域を強調表示色で塗りつぶし、塗りつぶした領域の上に TextLayout を描画します。これは、強調表示テキストを表示するための 1 つの簡単な方法です。

Shape highlightRegion = layout.getLogicalHighlightShape(hit1,      hit2);  
graphics.setColor(HIGHLIGHT_COLOR);  
graphics.fill(highlightRegion);  
graphics.drawString(layout, 0, 0); 

4.4.6 レイアウトメトリックスの問い合わせ

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); 

4.4.7 複数の行にまたがるテキストの描画

TextLayout を使って、複数の行にまたがるテキストを表示することもできます。たとえば、1 つの段落を対象に、ある一定の幅で行を折り返して複数行のテキストとして表示できます。

この場合、テキストの各行を表す TextLayouts を直接作成することはしません。これらは、ユーザーに代わって LineBreakMeasurer が生成してくれます。双方向テキストでは、段落内のすべてのテキストを把握するまでは順序付けを適切に行うことができない場合があります。LineBreakMeasurer は、コンテキストに関する十分な情報をカプセル化するので、正しい TextLayouts を生成できます。

複数の行にまたがってテキストを表示する場合、行の長さは一般に表示領域の幅によって決まります。行ブレーク (行の折り返し) は、行を収めなければならないグラフィカルな幅に基づいて、行の開始場所と終了場所を決定するプロセスです。

もっとも一般的な方法は、各行に収まる最大数の単語を配置する方法です。この方法は、LineBreakMeasurer に実装されています。このほかに、より複雑な行ブレークの方法として、ハイフネーションを使う、段落内部での行の長さをできるだけそろえるなどの方法があります。Java 2D™ API は、これらの方法の実装は提供していません。

テキストの段落を複数の行に分割するには、段落全体に対して LineBreakMeasurer を構築し、次に nextLayout を呼び出してテキストを順次処理し、行ごとに TextLayouts を生成します。

LineBreakMeasurer は、この処理を行うために、テキスト内でのオフセットを保持しています。最初は、オフセットはテキストの先頭にあります。このオフセットは、nextLayout を呼び出すたびに、生成された TextLayout の文字カウントの分だけ移動します。オフセットがテキストの末尾に到達すると、nextLayoutnull を返します。

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(); 
} 

4.5 独自のテキストレイアウトメカニズムの実装

GlyphVector クラスは、独自のレイアウトメカニズムの処理結果を表示する手段を提供します。GlyphVector オブジェクトは、文字列を受け取って、この文字列をどのように表示するかを正確に計算するアルゴリズムの出力とみなすことができます。システムには組み込みのアルゴリズムが 1 つありますが、Java 2D™ API では独自にアルゴリズムを定義できます。

GlyphVector オブジェクトは、基本的にはグリフとグリフの位置の配列です。文字の代わりにグリフを使うのは、カーニング、合字などのレイアウトの特性を完全に制御できるようにするためです。たとえば、文字列「final」を表示するときに、先頭の fi という部分文字列を合字の fi で置き換えたいことがあります。この場合、GlyphVector オブジェクトは、元の文字列に含まれる文字の数より少ない数のグリフを持つことになります。

図 4-13図 4-14 は、レイアウトメカニズムによって GlyphVector オブジェクトがどのように使われるかを示しています。図 4-13 は、デフォルトのレイアウトメカニズムを示しています。String に対して drawString が呼び出されると、組み込みレイアウトアルゴリズムは次の処理を行います。

  • Graphics2D コンテキスト内の現在の Font を使って、使用するグリフを決定する。
  • 各グリフを配置する位置を計算する。
  • 得られたグリフと位置に関する情報を GlyphVector に格納する。
  • GlyphVector を、実際のレンダリングを行うグリフレンダリングルーチンに渡す。

前の文で、このグラフィックスを説明しています。

図 4-13 組み込みのレイアウトアルゴリズムの使用

図 4-14 は、独自のレイアウトアルゴリズムを使う場合の処理を示しています。独自のレイアウトアルゴリズムを使うには、テキストをレイアウトするのに必要なすべての情報を収集しなければなりません。基本的な処理は次のとおりです。

  • Font を使って、使用するグリフを決定する
  • グリフを配置する位置を決定する
  • このレイアウト情報を GlyphVector に格納する

テキストをレンダリングするには、GlyphVectordrawString に渡します。drawString は、受け取った GlyphVector をグリフレンダラに渡します。図 4-14 では、独自のレイアウトアルゴリズムが fi という部分文字列を合字の fi で置き換えています。

独自のレイアウトアルゴリズムの使用

図 4-14 独自のレイアウトアルゴリズムの使用

4.6 フォント派生の作成

Font.deriveFont メソッドを使うと、既存の Font オブジェクトから、属性の異なる新しい Font オブジェクトを生成できます。よく使われるのは、既存の Font を変形して新しい派生 Font を作成する方法です。この手順は次のとおりです。

  • Font オブジェクトを作成します。
  • Font に適用する AffineTransform を作成します。
  • Font.deriveFont を呼び出し、AffineTransform を渡します。

この方法によって、独自のサイズの Font や既存 Font の歪んだ種類を簡単に作成できます。

次のコードでは、AffineTransform を適用し、フォント Helvetica の歪んだ種類を作成します。次に、新しい派生フォントを使って文字列をレンダリングします。

        // Create a transformation for the font. 
AffineTransform fontAT = new AffineTransform(); 
fontAT.setToShear(-1.2, 0.0); 
// Create a Font Object. 
Font theFont = new Font("Helvetica", Font.PLAIN, 1); 
// Derive a new font using the shear transform 
theDerivedFont = theFont.deriveFont(fontAT); 
// Add the derived font to the Graphics2D context 
g2.setFont(theDerivedFont); 
// Render a string using the derived font 
g2.drawString(“Java”, 0.0f, 0.0f); 

 


目次 | 前へ | 次へ

Copyright © 1993, 2013, Oracle and/or its affiliates. All rights reserved.