3

I need to draw text with Graphics#drawString

I'm drawing on top of a JPanel that changes both in width and height (by dragging).

I'm looking for a solution to generate bounds, so that I can warp the lines automatically and adapt the text accordingly, without overflows.

I figured I could just hardcode it myself by getting the length in pixels with fontMetrics, however, I would rather have a component that does this automatically (drawString also doesn't support '\n').

In the docs as well as in this other answer I found this:

Graphics2D g = ...;
Point2D loc = ...;
Font font = Font.getFont("Helvetica-bold-italic");
FontRenderContext frc = g.getFontRenderContext();
TextLayout layout = new TextLayout("This is a string", font, frc);
layout.draw(g, (float)loc.getX(), (float)loc.getY());

Rectangle2D bounds = layout.getBounds();
bounds.setRect(bounds.getX()+loc.getX(),
              bounds.getY()+loc.getY(),
              bounds.getWidth(),
              bounds.getHeight());
g.draw(bounds);

Which does draw the string and the bounds, but they have no effect, so no luck here.

Any class I could use?

Community
  • 1
  • 1
Lory A
  • 520
  • 1
  • 5
  • 17

2 Answers2

8

Any class I could use?

Another option is to use a LineBreakMeasurer:

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.text.*;
import java.util.Objects;
import javax.swing.*;
import javax.swing.border.Border;

public final class LineBreakMeasurerTest {
  private static final String TEXT = "1234567890 ABCDEFG HIJKLMN OPQRSTU VWXYZ";
  private final JLabel    lbl1 = new JLabel(TEXT);
  private final JTextArea lbl2 = new JTextArea(TEXT);
  private final JLabel    lbl3 = new WrappingLabel(TEXT);
  public JComponent makeUI() {
    Border b = BorderFactory.createLineBorder(Color.GREEN,  5);
    lbl1.setBorder(
        BorderFactory.createTitledBorder(b, "JLabel"));
    lbl2.setBorder(
        BorderFactory.createTitledBorder(b, "JTextArea"));
    lbl3.setBorder(
        BorderFactory.createTitledBorder(b, "LineBreakMeasurer"));

    lbl2.setFont(lbl1.getFont());
    lbl2.setEditable(false);
    lbl2.setLineWrap(true);
    lbl2.setWrapStyleWord(true);
    lbl2.setBackground(lbl1.getBackground());

    JPanel p = new JPanel(new GridLayout(3, 1));
    p.add(lbl1);
    p.add(lbl2);
    p.add(lbl3);
    return p;
  }

  public static void main(String... args) {
    EventQueue.invokeLater(() -> {
      UIManager.put("swing.boldMetal", Boolean.FALSE);
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new LineBreakMeasurerTest().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

class WrappingLabel extends JLabel {
  //TEST: private AffineTransform at = AffineTransform.getScaleInstance(.8, 1d);
  protected WrappingLabel(String text) {
    super(text);
  }
  @Override protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setPaint(getForeground());
    Insets i = getInsets();
    float x = i.left;
    float y = i.top;
    int w = getWidth() - i.left - i.right;

    AttributedString as = new AttributedString(getText());
    //TEST: as.addAttribute(TextAttribute.FONT, g2.getFont());
    //TEST: as.addAttribute(TextAttribute.TRANSFORM, at);
    AttributedCharacterIterator aci = as.getIterator();
    FontRenderContext frc = g2.getFontRenderContext();
    LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);

    while (lbm.getPosition() < aci.getEndIndex()) {
      TextLayout tl = lbm.nextLayout(w);
      tl.draw(g2, x, y + tl.getAscent());
      y += tl.getDescent() + tl.getLeading() + tl.getAscent();
    }
    g2.dispose();
  }
}
aterai
  • 9,658
  • 4
  • 35
  • 44
  • If you want the text centered, you can use this instead of `x`, `float xPos = (float) (x + ((getWidth() - tl.getBounds().getWidth()) / 2))` – Mr. Polywhirl May 08 '19 at 12:50
2

Simplest solution: use a JTextArea.

You can rig it up so that it doesn't even look like a JTextArea by setting background to null, by making it nonfocusable, and it will automatically wrap words if you call setLineWrap(true) and setWrapStyleWord(true) on it. To have it fill the JPanel, give the panel a BorderLayout, and add the JTextArea BorderLayout.CENTER. You can even set a margin if desired.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Thanks for answering. I tought of that, but is there no other way? I'd have a lot of trouble doing that, as it's not one united text. It's a lot of small strings, not alligned, with different format, color, specs. Here's a much simplified schema with a few: http://imgur.com/4zKTppM The red table is imaginary and drawn only to show the alignments. The strings update independently, this panel is VERY small and there are a quite a lot of them.. – Lory A Dec 13 '16 at 02:32