目次 | 前の項目 | 次の項目 | Java 2D API |
テキストを表示するには、適切なグリフと合字を使ってテキストの形状を決定し、配置しなければなりません。 このプロセスのことを、「テキストレイアウト」と呼びます。 テキストレイアウトのプロセスには、次のものが含まれます。
テキストをレイアウトするのに使われる情報は、キャレットの配置、ヒット検出、強調表示などのテキスト操作でも必要になります。国際市場に展開できるソフトウェアを開発するには、適切な書記法の規則に従ってさまざまな言語でテキストをレイアウトしなければなりません。
「グリフ」とは、1 つ以上の文字の視覚的表現のことです。 グリフの形状、サイズ、および位置は、そのグリフが置かれたコンテキストに依存します。 フォントと字体によっては、単一の文字または複数の文字の組み合わせを表すのに、多くの異なるグリフが使われることがあります。たとえば、手書きの筆記体によるテキストでは、隣接する文字とどのように結び付くかによって、1 つの文字がさまざまな形状を取ることがあります。
一部の書記法、特にアラビア語では、グリフのコンテキストを常に考慮しなければなりません。 英語の場合と異なり、アラビア語では筆記体の使用は不可欠であり、筆記体を使用せずにテキストを表示することはできません。
これらの筆記体は、コンテキストによって大幅に形状が変わる可能性があります。 たとえば、アラビア文字の heh には、次の図 4-2 に示すように 4 つの筆記体があります。
これら 4 つの形は、それぞれ非常に異なっています。 このように形状が変化していることは、英語の筆記体の場合でも基本的に同じです。コンテキストによっては、2 つのグリフが形状を大きく変化させて、単一のグリフを形成することもあります。 この種の融合されたグリフは、「合字」と呼ばれます。 たとえば、ほとんどの英語フォントには、図 4-3 に示すような合字「fi」があります。 この融合されたグリフでは、単に 2 つの文字を並べるのではなく、文字「f」の突き出た部分を考慮し、次の「i」と並べたときに自然に見えるように、2 つの文字を結合しています。
合字は、アラビア語でも使われており、一部の合字の使用は不可欠です。 つまり、適切な合字を使用せずに、特定の文字の組み合わせを表示することはできません。 アラビア文字から形成される合字は、英語の場合よりも形状がさらに大きく変化しています。 たとえば、図 4-4 は、となり合った 2 つのアラビア文字がどのような合字を形成するかを示したものです。
Java プログラミング言語では、テキストは Unicode 文字エンコーディングを使って符号化されます。 Unicode 文字エンコーディングを使うテキストは、「論理的順序」に従ってメモリに格納されます。 論理的順序とは、文字や単語を読み書きする順序のことです。 論理的順序は、グリフを表示する順序である「視覚的順序」と必ずしも同じではありません。ある特定の書記法 (筆記) でのグリフの視覚的順序は、「筆記順序」と呼ばれます。 たとえば、ローマ字テキストの筆記順序は左から右で、アラビア語とヘブライ語の筆記順序は右から左です。
書記法によっては、筆記順序に加えて、テキスト行にグリフや単語を配列するための規則を持つものがあります。 たとえば、アラビア語とヘブライ語では、文字は右から左へと並べられますが、数字は左から右へと並べられます。 したがって、英語のテキストが埋め込まれていない場合でも、アラビア語とヘブライ語は本当の意味で双方向言語であると言うことができます。
書記法の視覚的順序は、複数の言語が混在する場合でも維持しなければなりません。 このことを示しているのが、英語の文の中にアラビア語の語句が埋め込まれた図 4-5 です。
注: 次の例とそのあとのいくつかの例では、アラビア語とヘブライ語のテキストを大文字で表し、空白は下線で表しています。 各図には、メモリに格納されている文字の表現 (論理的順序の文字) と、実際に表示される文字の表現 (視覚的順序の文字) の 2 つの部分があります。 文字ボックスの下の数字は、挿入オフセットを表します。
アラビア語の単語は英語の文の一部ですが、アラビア語の筆記順序である左から右へと記述されています。 イタリック体のアラビア語の単語は、プレーンテキストのアラビア語の単語より論理的にあとにあるので、視覚的にはプレーンテキストのアラビア語の左側にあります。左から右に記述するテキストと右から左に記述するテキストが混在する行を表示する場合は、「基準方向」が重要です。 基準方向とは、主要な書記法の筆記順序のことです。 たとえば、テキストが主に英語で書かれていて、その中にアラビア語がいくつか埋め込まれている場合、基準方向は左から右になります。 一方、テキストが主にアラビア語で書かれていて、いくつかの英語や数字が埋め込まれている場合は、基準方向は右から左になります。
通常の方向のテキストの一部を表示するときの順序は、基準方向によって決まります。 図 4-5 に示した例では、基準方向は左から右です。 この例には 3 つの方向があり、 文頭の英語のテキストは左から右へ、アラビア語のテキストは右から左へ、ピリオドは左から右へ記述されています。
テキストの流れの中にグラフィックが埋め込まれることもあります。 テキストの流れと行の折り返しに与える影響という点では、これらのインライングラフィックは、グリフと同じように動作します。 インライングラフィックが文字の流れの中で適切な場所に表示されるようにするには、グリフと同じ双方向レイアウトアルゴリズムを使って、インライングラフィックを配置する必要があります。
1 行中のグリフの順序を決定するために使われる正確なアルゴリズムの詳細は、『The Unicode Standard, Version 2.0』の項 3.11「Bidirectional Algorithm」の説明を参照してください。
モノスペースフォントを使っている場合は別ですが、1 つのフォントでも文字によって幅は異なります。 したがって、テキストの配置と寸法決定では、使われている文字数ではなく、どの文字が使われているかを正確に把握する必要があります。 たとえば、プロポーショナルフォントで表示される数字の列を右揃えする場合、空白をいくつか追加することによってテキストを配置することはできません。 列を適切に揃えるには、各数字の正確な幅を調べ、その幅に応じて適切に調整を行う必要があります。テキストは、複数のフォントや、ボールド、イタリックなどのさまざまな字体を使って表示されることがあります。 この場合は、どのような字体が使われているかによって、同じ文字でも形状や幅が異なる可能性があります。 テキストの適切な配置、寸法測定、およびレンダリングを行うには、各文字とその文字に適用される字体の両方を把握する必要があります。 TextLayout は、この処理をユーザに代わって行います。
ヘブライ語やアラビア語などの言語でテキストを適切に表示するには、各文字の寸法を測定し、隣接する文字のコンテキストの中で文字を配置する必要があります。 文字の形状と位置はコンテキストによって変わることがあるので、コンテキストを考慮せずにこれらのテキストの寸法決定と配置を行うと、得られる結果は不適切なものになります。
表示されているテキストを編集できるためには、次のことが可能でなければなりません。
編集可能なテキストでは、現在の挿入ポイントをグラフィカルに表すために「キャレット」が使われます。 挿入ポイントとは、テキスト内で新しい文字が挿入される位置のことです。 通常、キャレットは、2 つのグリフの間の点滅する縦線で表示されます。 新しい文字は、このキャレットの場所に挿入され、表示されます。キャレット位置の計算は、特に双方向テキストの場合には複雑になることがあります。 双方向テキストでは、文字オフセットに対応する 2 つのグリフが互いに隣接して表示されるわけではないので、方向の境界上の挿入オフセットは、キャレット位置として 2 つの可能性を持ちます。 このことを示しているのが、図 4-6 です。 この図では、キャレットがどのグリフに対応しているかを示すために、キャレットが角括弧で表示されています。
文字オフセット 8 は、_ のあと、「A」の前の場所に対応しています。 ここでユーザがアラビア語の文字を入力すると、入力した文字のグリフは「A」の右 (前) に表示されます。 英語の文字を入力すると、そのグリフは「 _ 」の右 (後) に表示されます。このような状況に対処するために、一部のシステムでは、強い (主) キャレットと弱い (副) キャレットのデュアルキャレットを表示します。 強いキャレットは、文字の方向がテキストの基準方向と同じ場合に、挿入された文字が表示される場所を示します。 弱いキャレットは、文字の方向が基準方向と逆の場合に、挿入された文字が表示される場所を示します。 TextLayout はデュアルキャレットを自動的にサポートしますが、JTextComponent はデュアルキャレットをサポートしていません。
双方向テキストを対象とする場合は、文字オフセットの前にグリフの幅を単純に加えるだけでは、キャレット位置を計算することはできません。 このような方法を使用した場合、図 4-7 に示すように、キャレットが間違った場所に描画されてしまいます。
キャレットを適切に配置するには、オフセットの左側にあるすべてのグリフの幅を追加するとともに、現在のコンテキストを考慮する必要があります。 コンテキストを考慮に入れないと、グリフのメトリックスが表示と一致しなくなる可能性があります。 ( どのグリフが使われるかは、コンテキストによっても左右されるからです。)
どのテキストエディタでも、ユーザは矢印キーを使ってキャレットを移動できます。 ユーザは、自分が押した矢印キーの方向にキャレットが移動することを期待しています。 左から右に記述するテキストでは、挿入オフセットの移動も単純です。 右矢印キーが押されたら挿入オフセットを 1 つ増やし、左矢印キーが押されたら挿入オフセットを 1 つ減らします。 双方向テキストや、合字が含まれたテキストでは、矢印キーを押すと、方向の境界でキャレットがいくつかのグリフを飛び越え、方向が逆になる部分では逆方向にキャレットが移動することになります。双方向テキストでキャレットを円滑に移動するには、テキストの方向を考慮する必要があります。 右矢印キーが押されたときに挿入オフセットを 1 つ増やし、左矢印キーが押されたときに挿入オフセット 1 つ減らすだけでは不十分です。 現在の挿入オフセットが、右から左に記述する文字の中にある場合は、右矢印キーが押されたら挿入オフセットを減らし、左矢印キーが押されたら挿入オフセットを増やす必要があります。
方向の境界にまたがったキャレットの移動は、さらに複雑になります。 図 4-8 は、ユーザが矢印キーを使って移動中に、方向の境界を越えるとどのようなことが起こるかを示しています。 表示されているテキスト内で右に 3 つ移動すると、それぞれオフセット 7、19、および 18 の文字に移動することになります。
グリフによっては、その間にキャレットを置くことができないものがあります。 この場合は、これらのグリフがあたかも 1 つの文字を表しているかのように、キャレットを移動する必要があります。 たとえば、「o」とウムラウトが 2 つの独立した文字によって表されている場合、間にキャレットを置くことはできません (詳細は、『The Unicode Standard, Version 2.0』の第 5 章を参照)。TextLayout は、双方向テキストでのキャレットの円滑な移動を簡単に実現するためのメソッド (getNextRightHit と getNextLeftHit) を提供しています。
デバイス空間内での場所は、しばしばテキストオフセットに変換しなければなりません。 たとえば、選択可能なテキスト上でユーザがマウスをクリックした場合、マウスの場所はテキストオフセットに変換され、選択範囲の一方の端として使われます。 これは、論理的にはキャレットの配置と逆の操作です。双方向テキストを対象とする場合、ディスプレイ内の視覚的には単一の場所が、元のテキストでは 2 つの異なるオフセットに対応することがあります。 このことを示しているのが、図 4-9 です。
視覚的には単一の場所が、2 つの異なるオフセットに対応することがあるので、双方向テキストのヒット判定では、目的の場所のグリフが見つかるまでグリフの幅を計算し、該当するグリフが見つかったらその位置を文字オフセットに対応付けるという処理だけでは不十分です。 2 つの選択肢のうち適切なものを選ぶには、ヒットがあったのはどちら側かを検出する必要があります。TextLayout.hitTestChar を使うと、ヒット判定を行うことができます。 ヒット情報は TextHitInfo オブジェクトの中にカプセル化され、ヒットがあったのはどちら側かについての情報もその中に含まれています。
選択範囲の文字は、強調表示領域によってグラフィカルに表示されます。 強調表示領域では、グリフは反転表示されるか、または異なる背景色の上に表示されます。双方向テキストの場合は、キャレット同様、強調表示領域も単方向テキストの場合より複雑になります。 双方向テキストでは、隣接する範囲の文字でも、表示されたときに強調表示領域が隣接しないことがあります。 逆に、強調表示領域が、視覚的に隣接する範囲のグリフを示している場合でも、単一の隣接する範囲の文字に対応するとは限りません。
このため、双方向テキストで選択部分を強調表示する場合は、次の 2 つの方法が存在することになります。
論理的強調表示の方が実装は容易です。 これは、選択された文字がテキスト内で常に隣接するためです。
使用する JavaTM API に応じて、テキストレイアウトの制御を選択して増減することができます。
- ひとまとまりのテキストを表示するだけの場合や、編集可能なテキストの制御が必要な場合は、ユーザに代わってテキストレイアウトを実行する JTextComponent を使用できます。 JTextComponent は、ほとんどの国際アプリケーションのニーズに対処できるように設計されており、双方向テキストをサポートしています。 JTextComponent についての詳細は、「The Java Tutrial」の JFC/Swing パッケージの使用法を参照してください。
- 簡単なテキスト文字列を表示したい場合は、Graphics2D.drawString を呼び出し、文字列のレイアウトを Java 2D に行わせることができます。 drawString は、字体付き文字列や双方向テキストを含む文字列のレンダリングにも使用できます。 Graphics2D によるテキストのレンダリングについての詳細は、「グラフィックスプリミティブのレンダリング」を参照してください。
- 独自のテキスト編集ルーチンを実装する場合は、TextLayout を使うと、テキストレイアウト、強調表示、およびヒット検出を管理できます。 TextLayout が提供する機能を利用すれば、さまざまなフォント、言語、および双方向テキストが混在するテキスト文字列など、ほとんどの場合に対処できます。 TextLayout の使用法についての詳細は、「テキストレイアウトの管理」を参照してください。
- テキストの形状決定と配置を完全に制御したい場合は、Font を使って独自の GlyphVector を構築し、これらを Graphics2D を通じてレンダリングすることができます。 独自のテキストレイアウト機構の実装方法についての詳細は、「独自のテキストレイアウト機構の実装」を参照してください。
一般に、テキストレイアウト操作をユーザが行う必要はありません。 ほとんどのアプリケーションでは、JTextComponent が、静的で編集可能なテキストを表示するための最良の解決方法です。 ただし、JTextComponent は、双方向テキストでのデュアルキャレットや、隣接してない選択部分の表示をサポートしていません。 アプリケーションでこれらの機能が必要な場合、または独自のテキスト編集ルーチンを実装したい場合は、Java 2D テキストレイアウト API を使用できます。