【Java】【Swing】 jLabel の折り返しを考える [2]

■ はじめに

https://blogs.yahoo.co.jp/dk521123/36831910.html
の「2) 独自で実装する」でうまくいかないケース(『「あいう。」の後に自動的に改行されてしまう』)があったので
別の実装方法を考える。

■ 原因

 * LineBreakMeasurerで、折り返し方法を定義するBreakIteratorが、
   文字単位で分割されるように指定していなかったため。
LineBreakMeasurerのAPI
http://e-class.center.yuge.ac.jp/jdk_docs/ja/api/java/awt/font/LineBreakMeasurer.html
http://e-class.center.yuge.ac.jp/jdk_docs/ja/api/java/awt/font/LineBreakMeasurer.html#LineBreakMeasurer(java.text.AttributedCharacterIterator, java.text.BreakIterator, java.awt.font.FontRenderContext)
public LineBreakMeasurer(AttributedCharacterIterator text,
                         BreakIterator breakIter, // ★ここ : breakIter - 改行を定義する BreakIterator★
                         FontRenderContext frc)

■ 解決案

 * BreakIteratorで文字で分割するようにする。

修正前

AttributedCharacterIterator attributedCharacterIterator = attributedString.getIterator();
FontRenderContext fontRenderContext = g2.getFontRenderContext();
LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(attributedCharacterIterator, fontRenderContext);

修正後

AttributedCharacterIterator attributedCharacterIterator = attributedString.getIterator();
BreakIterator characterBreakIterator = BreakIterator.getCharacterInstance();// ★ここ★
FontRenderContext fontRenderContext = g2.getFontRenderContext();
LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(attributedCharacterIterator, characterBreakIterator,
    fontRenderContext);

■ サンプル

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.BreakIterator;

import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.border.LineBorder;

public class WrappedLabel extends JLabel {

  private static final long serialVersionUID = 1L;

  public static void main(String[] args) {
    JFrame frame = new JFrame("WrappedLabel Demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Box box = Box.createVerticalBox();

    String text = "あいう。1234567890123456789012345678901234567890";

    JLabel jLabel = new JLabel(text);
    jLabel.setBorder(new LineBorder(Color.BLACK, 2));
    jLabel.setPreferredSize(new Dimension(50, 100));
    box.add(jLabel);
    box.add(Box.createVerticalGlue());

    String text2 = "あいう。1234567890123456789012345678901234567890";

    WrappedLabel wrappedLabel = new WrappedLabel(text2);
    wrappedLabel.setBorder(new LineBorder(Color.BLACK, 2));
    wrappedLabel.setPreferredSize(new Dimension(50, 100));
    box.add(wrappedLabel);

    frame.add(box);
    frame.setBounds(100, 200, 400, 400);
    frame.setVisible(true);
  }

  public WrappedLabel(String text) {
    super(text);
  }

  @Override
  protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setPaint(this.getForeground());
    Insets insets = this.getInsets();
    float x = insets.left;
    float y = insets.top;
    int width = this.getWidth() - insets.left - insets.right;
    AttributedString attributedString = new AttributedString(this.getText());
    attributedString.addAttribute(TextAttribute.FONT, this.getFont());
    AttributedCharacterIterator attributedCharacterIterator = attributedString.getIterator();
    BreakIterator characterBreakIterator = BreakIterator.getCharacterInstance();
    FontRenderContext fontRenderContext = g2.getFontRenderContext();
    LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(attributedCharacterIterator, characterBreakIterator,
        fontRenderContext);
    while (lineBreakMeasurer.getPosition() < attributedCharacterIterator.getEndIndex()) {
      TextLayout textLayout = lineBreakMeasurer.nextLayout(width);
      textLayout.draw(g2, x, y + textLayout.getAscent());
      y += textLayout.getDescent() + textLayout.getLeading() + textLayout.getAscent();
    }

    g2.dispose();
  }
}

余談

 * 上記「■ 解決案」を気づく前に、以下の「別解サンプル」で解決しようと思った。。。

別解サンプル

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;

import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.border.LineBorder;

public class WrappedLabel extends JLabel {

  private static final long serialVersionUID = 1L;

  public static void main(String[] args) {
    JFrame frame = new JFrame("WrappedLabel Demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Box box = Box.createVerticalBox();

    String text = "あいう。1234567890123456789012345678901234567890";

    JLabel jLabel = new JLabel(text);
    jLabel.setBorder(new LineBorder(Color.BLACK, 2));
    jLabel.setPreferredSize(new Dimension(50, 100));
    box.add(jLabel);
    box.add(Box.createVerticalGlue());

    String text2 = "あいう。1234567890123456789012345678901234567890";

    WrappedLabel wrappedLabel = new WrappedLabel(text2);
    wrappedLabel.setBorder(new LineBorder(Color.BLACK, 2));
    wrappedLabel.setPreferredSize(new Dimension(50, 100));
    box.add(wrappedLabel);

    frame.add(box);
    frame.setBounds(100, 200, 400, 400);
    frame.setVisible(true);
  }

  public WrappedLabel(String text) {
    super(text);
  }

  @Override
  protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setPaint(this.getForeground());
    Insets insets = this.getInsets();
    float x = insets.left;
    float y = insets.top;
    int width = this.getWidth() - insets.left - insets.right;

    FontMetrics fontMetrics = g.getFontMetrics();
    FontRenderContext fontRenderContext = g2.getFontRenderContext();
    String text = this.getText();

    if (text == null || text.isEmpty() || text.length() == 1) {
      return;
    }

    int start = 0;
    int end = 1;
    while (true) {
      int targetWidth = fontMetrics.stringWidth(text.substring(start, end));
      if (width < targetWidth) {
        TextLayout textLayout = new TextLayout(text.substring(start, end - 1), this.getFont(), fontRenderContext);
        textLayout.draw(g2, x, y + textLayout.getAscent());
        y += textLayout.getDescent() + textLayout.getLeading() + textLayout.getAscent();
        start = end;
      } else if (text.length() == end) {
        TextLayout textLayout = new TextLayout(text.substring(start, end), this.getFont(), fontRenderContext);
        textLayout.draw(g2, x, y + textLayout.getAscent());
        break;
      }
      end++;
    }

    g2.dispose();
  }
}

関連記事

jLabel の折り返しを考える [1]

https://blogs.yahoo.co.jp/dk521123/36831910.html