目次||

第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チュートリアルの「Using the JFC/Swing Packages」を参照してください。

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つにマップされる名前です。Fontを指定する場合は、論理名ではなくフォント・フェース名を使用する必要があります。getNameを呼び出すと、Fontから論理名を取得できます。プラットフォーム上で使用可能な具体的なフォントにマップされた論理名のリストを取得するには、java.awt.Toolkit.getFontListを呼び出してください。
  • Fontオブジェクトのファミリ名は、Helveticaなど、複数のフェースにまたがって文字体裁のデザインを決定するフォント・ファミリの名前です。ファミリ名は、getFamilyメソッドを通じて取得します。
  • Fontオブジェクトのフォント・フェース名は、システムにインストールされている実際のフォントを指します。JDKでフォントを指定するときは、このフォント・フェース名を使用する必要があります。フォント・フェース名は、単にフォント名と呼ばれることがよくあります。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チュートリアルの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つの要素からなるShapeの配列を返します。このうち要素0には強いキャレットが、要素1には弱いキャレット(存在する場合)が含まれています。デュアル・キャレットは、これらのShapeを両方ともレンダリングするだけで表示できます。キャレットは、自動的に適切な位置にレンダリングされます。

独自のキャレット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つの段落を対象に、ある一定の幅で行を折り返して複数行のテキストとして表示できます。

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

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

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

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

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, 2020, Oracle and/or its affiliates. All rights reserved.