Как подсчитать количество строк в JTextArea, включая те, которые вызваны упаковкой?

У меня есть JTextArea, для которого я установил word-wrap и wrap-style-word в true. Я хочу "упаковать" JTextArea на минимально возможную высоту с заданной шириной.

Чтобы сделать это, я планирую вычислять высоту шрифта, используя...

  Font font = jTextArea.getFont();
  FontMetrics fontMetrics = jTextArea.getFontMetrics(font);
  int lineHeight = fontMetrics.getAscent() + fontMetrics.getDescent();

... и затем умножьте это на количество строк, используемых в JTextArea. Проблема в том, что JTextArea.getLineCount() подсчитывает количество строк, игнорирующих обернутые строки.

Как подсчитать количество строк, используемых в JTextArea, включая те, которые вызваны переносом слов?

Вот какой-то демо-код, чтобы облегчить общение с этой проблемой. У меня есть слушатель, который печатает количество строк при каждом изменении размера окна. На данный момент он всегда печатает 1, но я хочу компенсировать перенос слов и распечатывать, сколько строк фактически используется.

EDIT: я включил решение проблемы в код ниже. Метод static countLines дает решение.

package components;                                                                           

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.text.*;
import javax.swing.*;

public class JTextAreaLineCountDemo extends JPanel {                                          
  JTextArea textArea;                                                                         

  public JTextAreaLineCountDemo() {                                                           
    super(new GridBagLayout());                                                               

    String inputStr = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmo";
    textArea = new JTextArea(inputStr);                                                       
    textArea.setEditable(false);                                                              
    textArea.setLineWrap(true);                                                               
    textArea.setWrapStyleWord(true);                                                          

    // Add Components to this panel.                                                          
    GridBagConstraints c = new GridBagConstraints();                                          
    c.gridwidth = GridBagConstraints.REMAINDER;                                               

    c.fill = GridBagConstraints.BOTH;                                                         
    c.weightx = 1.0;                                                                          
    c.weighty = 1.0;                                                                          
    add(textArea, c);                                                                         

    addComponentListener(new ComponentAdapter() {                                             
      @Override                                                                               
      public void componentResized(ComponentEvent ce) {                 
        System.out.println("Line count: " + countLines(textArea));                         
      }                                                                                       
    });                                                                                       
  }                                                                                           

  private static int countLines(JTextArea textArea) {
    AttributedString text = new AttributedString(textArea.getText());
    FontRenderContext frc = textArea.getFontMetrics(textArea.getFont())
        .getFontRenderContext();
    AttributedCharacterIterator charIt = text.getIterator();
    LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(charIt, frc);
    float formatWidth = (float) textArea.getSize().width;
    lineMeasurer.setPosition(charIt.getBeginIndex());

    int noLines = 0;
    while (lineMeasurer.getPosition() < charIt.getEndIndex()) {
      lineMeasurer.nextLayout(formatWidth);
      noLines++;
    }

    return noLines;
  }

  private static void createAndShowGUI() {                                                    
    JFrame frame = new JFrame("JTextAreaLineCountDemo");                                      
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);                                     

    frame.add(new JTextAreaLineCountDemo());                                                  

    frame.pack();                                                                             
    frame.setVisible(true);                                                                   
  }                                                                                           

  public static void main(String[] args) {                                                    
    javax.swing.SwingUtilities.invokeLater(new Runnable() {                                   
      public void run() {                                                                     
        createAndShowGUI();                                                                   
      }                                                                                       
    });                                                                                       
  }                                                                                           
}                                                                                             

Ответ 1

Вы можете использовать LineBreakMeasurer Класс.

Класс LineBreakMeasurer позволяет стилизованный текст, который будет разбит на строки (или сегментов), которые соответствуют конкретный визуальный прогресс. Это полезно для клиентов, которые хотят отображать абзац текста, который удельная ширина, называемая обертыванием width.LineBreakMeasurer реализует наиболее часто используемую политику: слово, которое помещается внутри упаковки ширина находится на линии. Если первое слово не подходит, тогда все символы, которые соответствуют ширина обертывания помещается на линию. По меньшей мере один символ помещается на каждой строке.

Ответ 2

Мне кажется, что это не может быть ответ. Если вы изменяете шрифт и расширяете текст, то linecount становится неверным.

EDIT: решение Вы должны установить шрифт для textarea и для атрибутной строки. Теперь linecount верна. Решение в коде.

package lineBreak;                                                                        

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.text.*;
import javax.swing.*;

public class JTextAreaLineCountDemo extends JPanel {                                          
    JTextArea textArea;      

    static Font f = new Font("Helvetiva", Font.ITALIC, 50);                                                                         

  public JTextAreaLineCountDemo() {                                                           
    super(new GridBagLayout());                                                               

    String inputStr = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmo, Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmo, Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmo, Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmo";
    textArea = new JTextArea(inputStr);     

    textArea.setEditable(false);                                                              
    textArea.setLineWrap(true);                                                               
    textArea.setWrapStyleWord(true);                                                          

    // Add Components to this panel.                                                          
    GridBagConstraints c = new GridBagConstraints();                                          
    c.gridwidth = GridBagConstraints.REMAINDER;                                               

    c.fill = GridBagConstraints.BOTH;                                                         
    c.weightx = 1.0;                                                                          
    c.weighty = 1.0;                                                                          
    add(textArea, c);                                                                         

    addComponentListener(new ComponentAdapter() {                                             
      @Override                                                                               
      public void componentResized(ComponentEvent ce) {   
          **textArea.setFont(new Font("Arial", Font.BOLD, 22));**
        System.out.println("Line count: " + countLines(textArea));                         
      }                                                                                       
    });                                                                                       
  }                                                                                           

  private static int countLines(JTextArea textArea) {
    AttributedString text = new AttributedString(textArea.getText());
    text.addAttribute(TextAttribute.FONT, f);
    FontRenderContext frc = textArea.getFontMetrics(textArea.getFont())
        .getFontRenderContext();
    AttributedCharacterIterator charIt = text.getIterator();
    LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(charIt, frc);
    float formatWidth = (float) textArea.getSize().width;
    lineMeasurer.setPosition(charIt.getBeginIndex());

    int noLines = 0;
    while (lineMeasurer.getPosition() < charIt.getEndIndex()) {
      lineMeasurer.nextLayout(formatWidth);
      noLines++;
    }

    return noLines;
  }

  private static void createAndShowGUI() {                                                    
    JFrame frame = new JFrame("JTextAreaLineCountDemo");                                      
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);                                     

    frame.add(new JTextAreaLineCountDemo());                                                  

    frame.pack();                                                                             
    frame.setVisible(true);                                                                   
  }                                                                                           

  public static void main(String[] args) {                                                    
    javax.swing.SwingUtilities.invokeLater(new Runnable() {                                   
      public void run() {                                                                     
        createAndShowGUI();                                                                   
      }                                                                                       
    });                                                                                       
  }                                                                                           
}

Ответ 3

Ваш метод countLines почти сработал у меня, но мне пришлось сделать несколько настроек, чтобы он работал правильно в моем случае. Я предполагаю, что вы используете шрифт по умолчанию и не имеете границы на вашем JTextArea, но с использованием шрифта, отличного от стандартного, или с границей (или обоими, как было в моем случае) приведет к тому, что ваш метод countLines верните неправильный номер. Ниже приведена моя обновленная версия, которая учитывает оба этих фактора (а также использует textArea.getWidth() вместо textArea.getSize().width).

private static int countLines(JTextArea textArea)
{
    AttributedString text = new AttributedString(textArea.getText());
    text.addAttribute(TextAttribute.FONT, textArea.getFont());
    FontRenderContext frc = textArea.getFontMetrics(textArea.getFont()).getFontRenderContext();
    AttributedCharacterIterator charIt = text.getIterator();
    LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(charIt, frc);
    Insets textAreaInsets = textArea.getInsets();
    float formatWidth = textArea.getWidth() - textAreaInsets.left - textAreaInsets.right;
    lineMeasurer.setPosition(charIt.getBeginIndex());

    int noLines = 0;
    while (lineMeasurer.getPosition() < charIt.getEndIndex())
    {
        lineMeasurer.nextLayout(formatWidth);
        noLines++;
    }

    return noLines;
}

Вся благодарность за распознавание того, что AttributedString, необходимое для установки шрифта на шрифт JTextArea, идет в Jenny