11

My Program overrides public void paint(Graphics g, int x, int y); in order to draw some stings using g.drawString(someString, x+10, y+30);

Now someString can be quite long and thus, it may not fit on one line.

What is the best way to write the text on multiple line.
For instance, in a rectangle (x1, y1, x2, y2)?

Burkhard
  • 14,596
  • 22
  • 87
  • 108

5 Answers5

10

Thanks to Epaga's hint and a couple of examples on the Net (not so obvious to find! I used mainly Break a Line for text layout), I could make a component to display wrapped text. It is incomplete, but at least it shows the intended effect.

class TextContainer extends JPanel
{
    private int m_width;
    private int m_height;
    private String m_text;
    private AttributedCharacterIterator m_iterator;
    private int m_start;
    private int m_end;

    public TextContainer(String text, int width, int height)
    {
        m_text = text;
        m_width = width;
        m_height = height;

        AttributedString styledText = new AttributedString(text);
        m_iterator = styledText.getIterator();
        m_start = m_iterator.getBeginIndex();
        m_end = m_iterator.getEndIndex();
    }

    public String getText()
    {
        return m_text;
    }

    public Dimension getPreferredSize()
    {
        return new Dimension(m_width, m_height);
    }

    public void paint(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;
        FontRenderContext frc = g2.getFontRenderContext();

        LineBreakMeasurer measurer = new LineBreakMeasurer(m_iterator, frc);
        measurer.setPosition(m_start);

        float x = 0, y = 0;
        while (measurer.getPosition() < m_end)
        {
            TextLayout layout = measurer.nextLayout(m_width);

            y += layout.getAscent();
            float dx = layout.isLeftToRight() ?
                    0 : m_width - layout.getAdvance();

            layout.draw(g2, x + dx, y);
            y += layout.getDescent() + layout.getLeading();
        }
    }
}

Just for fun, I made it fitting a circle (alas, no justification, it seems):

public void paint(Graphics g)
{
    super.paintComponent(g);

    Graphics2D g2 = (Graphics2D) g;
    FontRenderContext frc = g2.getFontRenderContext();

    LineBreakMeasurer measurer = new LineBreakMeasurer(m_iterator, frc);
    measurer.setPosition(m_start);

    float y = 0;
    while (measurer.getPosition() < m_end)
    {
        double ix = Math.sqrt((m_width / 2 - y) * y);
        float x = m_width / 2.0F - (float) ix;
        int width = (int) ix * 2;

        TextLayout layout = measurer.nextLayout(width);

        y += layout.getAscent();
        float dx = layout.isLeftToRight() ?
                0 : width - layout.getAdvance();

        layout.draw(g2, x + dx, y);
        y += layout.getDescent() + layout.getLeading();
    }
}

I am not too sure about dx computation, though.

PhiLho
  • 40,535
  • 6
  • 96
  • 134
3

java.awt.font.TextLayout might be helpful. Here's a snippet of their example code:

   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);

Otherwise you could always use a Swing text element to do the job for you, just pass in the Graphics you want it to paint into.

Epaga
  • 38,231
  • 58
  • 157
  • 245
  • I got that one already. My problem is, I don't want a rectangle around the text, but the text inside a given rectangle. – Burkhard Oct 27 '08 at 10:59
  • Note that TextLayout's constructor, undocumentedly, throws an exception if the string you pass is empty. – Ricky Clarkson Feb 22 '13 at 00:51
  • 1
    Use Shape labelShape = textLayout1.getOutline(null); and then g2d.fill(labelShape); they should eliminate the border – mk7 Dec 15 '15 at 19:50
2

Incrementally build your string, one word at a time, using Epaga's method to find the length of your string. Once the length is longer than your rectangle, remove the last word and print. Repeat until you run out of words.

It sounds like a bad algorithm, but for each line, it's really O(screenWidth/averageCharacterWidth) => O(1).

Still, use a StringBuffer to build your string!

Ryan Fox
  • 10,103
  • 5
  • 38
  • 48
  • 1
    This is not going to have a time complexity of O(1); O(n), perhaps, as you need to (at least) add a word (i.e. add a number of chars in the StringBuilder.append) until all the chars have been used up. – jamesh Oct 27 '08 at 12:43
  • I seem to be bad at expressing myself properly... I meant that each line is O(1). You can throw 1000 words at it, but, for each line, it'll stop adding words at the edge of the screen. Overall, yes, O(n). – Ryan Fox Oct 27 '08 at 18:17
  • It works only if you suppose the string will be at max 2 lines, when it will be more, it won't work. – Cyril N. Jan 12 '12 at 20:31
1

Had some trouble myself this is my solution:

Graphics2D g=....
FontRenderContext frc = g.getFontRenderContext();
TextLayout layout = new TextLayout(text, font, frc);
String[] outputs = text.split("\n");
for(int i=0; i<outputs.length; i++){
    g.drawString(outputs[i], 15,(int) (15+i*layout.getBounds().getHeight()+0.5));

Hope that helps.... simple but it works.

msj121
  • 2,812
  • 3
  • 28
  • 55
0

You can use a JLabel and embed the text with html.

JLabel.setText("<html>"+line1+"<br>"+line2);
PhiLho
  • 40,535
  • 6
  • 96
  • 134
michelemarcon
  • 23,277
  • 17
  • 52
  • 68