テキストを折り返し表示する
続いて、「長いテキストを折り返し表示する」ことについて考えてみましょう。テキストをAttributedStringとして用意し、描画の際にAttributedCharacterIteratorインスタンスを取り出すという点は同じです。
折り返し表示には、取り出されたAttributedCharacterIteratorから、一定幅分のテキストのレイアウトに関するオブジェクトを取り出し、描画する、といった作業を行っていきます。では、これも例を挙げて説明しましょう。
// import文を追加 import java.awt.font.*; import java.awt.geom.Rectangle2D; /* Sample クラス部分は省略 */ class G2Panel extends JPanel { private static final long serialVersionUID = 1L; public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; Font f1 = new Font("Dialog",Font.PLAIN,24); String str1 = "これは、折り返し表示をするサンプルです。"; AttributedString as1 = new AttributedString(str1); as1.addAttribute(TextAttribute.FONT,f1); AttributedCharacterIterator ac1 = as1.getIterator(); FontRenderContext fc1 = g2.getFontRenderContext(); LineBreakMeasurer m1 = new LineBreakMeasurer(ac1,fc1); float y = 50; // 最初の描画位置 while (m1.getPosition() < str1.length()) { TextLayout tl1 = m1.nextLayout(150); // 折り返し幅 tl1.draw(g2, 20, y); // 取得行の描画 y += tl1.getBounds().getHeight() + 10; // 次行との間隔 } // 以下はテキストの描画領域の枠線の表示 g2.setPaint(Color.red); m1.setPosition(0); Rectangle2D r1 = new Rectangle2D.Float( 20,50 - m1.nextLayout(150).getAscent(),150,y - 50); g2.draw(r1); } }

ここでは、修正したG2Panelクラスのみ掲載しておきました。str1に指定したテキストを、150ドット幅で折り返し表示しています。この幅は、自由に調整することができます。
LineBreakMeasurerとTextLayout
折り返し表示では、全部で3つのクラスが重要な働きをします。
- FontRenderContext
- LineBreakMeasurer
- TextLayout
Graphics2Dからこのインスタンスを取り出し、利用します。LineBreakMeasurerから取り出される、テキストのレイアウトに関する情報を管理するクラスです。LineBreakMeasurerから各行のTextLayoutを取り出し、これをもとに描画を行っていきます。 では、折り返し表示に関する処理部分を順に見ていきましょう。まず、処理に必要となるFontRenderContextと、LineBreakMeasurerを作成します。
FontRenderContext fc1 = g2.getFontRenderContext();
LineBreakMeasurer m1 = new LineBreakMeasurer(ac1,fc1);
Graphics2DからgetFontRenderContextでFontRenderContextを取得します。そして、このFontRenderContextと、あらかじめ用意しておいたAttributedCharacterIteratorを引数に指定して、LineBreakMeasurerインスタンスを作成します。
これで準備は整いました。いよいよ一切の描画に進みます。次のコードは、LineBreakMeasurerから1行ずつレイアウト・オブジェクトを取り出しては描画する処理を繰り返します。
while (m1.getPosition() < str1.length()) {
TextLayout tl1 = m1.nextLayout(150);
tl1.draw(g2, 20, y);
y += tl1.getBounds().getHeight() + 10;
}
whileの条件にあるgetPositionは、LineBreakMeasurerで「現在の位置」を示すものです。LineBreakMeasurerは、現在の位置に関する情報を持っており、そこから1行分のレイアウトを取り出すと、その後に現在位置が移動するようになっています。そこで、「現在位置が、テキストの長さより小さい(すなわち、テキスト内のどこかに現在位置がある)間は繰り返す」ようにしているのです。
繰り返しの中でまず行っているのは、LineBreakMeasurerから「次の行のレイアウト・オブジェクト」を取り出す処理です。これを行っているのがnextLayoutです。引数には、行末までの長さ(幅、ドット数)を指定します。これで、150ドット幅の次行レイアウト・オブジェクトを取り出します。
取り出されるのは、TextLayoutというクラスのインスタンスです。このTextLayoutのdrawメソッドを呼び出すことで、取り出した行のレイアウトをもとに描画を行います。引数には、描画先のGraphics2D、横位置、縦位置を指定します。
そして描画を行ったら、TextLayoutの領域オブジェクト(getBoundsで取り出されます)から高さ(その行の縦幅)を調べ、これに10を足した値を変数に加算しておきます。これが、次の行の縦位置となるわけです。
こうして、「次の行のレイアウトを取り出し、描画をしては、描いた行の縦幅を変数に足して描画の縦位置を増やしていく」ということを繰り返していくことで、一定幅で折り返す描画が行える、というわけです。
