3

I'm trying to draw random text with fontSize based on word frequency on the BufferedImage using the drawString(word, x, y) with random x,y values. Unfortunately it draws the random text that is overlaping. The image frame size is 1200 x 650 and the random numbers for x, y are between these values. Here's my code:

Random rand = new Random();
Font f = getRandomFont(fontSize);
FontMetrics metrics = graphics.getFontMetrics(f);
AffineTransform affinetransform = new AffineTransform();     
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
int textwidth = (int)(f.getStringBounds(word, frc).getWidth());
int textheight = (int)(f.getStringBounds(word, frc).getHeight());
graphics.setColor(Color.red);
graphics.setFont(f);
int x = textwidth + rand.nextInt(800);
int y = -textheight + rand.nextInt(800);
graphics.drawString(word, x , y );
TDG
  • 5,909
  • 3
  • 30
  • 51
James Ngondo
  • 51
  • 1
  • 6
  • Text is rendered from the base line up, so you really should be using -textheight, but using Font!etics#getAscent. You could also use Font#getStringBounds which will return a Rectangle2D which you could cache and use to determine if any other text has been rendered within the bounds of your new text – MadProgrammer Jan 09 '16 at 08:04
  • You should probably also have a look at [Measuring text](https://docs.oracle.com/javase/tutorial/2d/text/measuringtext.html) – MadProgrammer Jan 09 '16 at 08:05

2 Answers2

5

First, fonts are drawn around the "base line", that is, the y position represents the baseline, so the text can grow above it and below it...

baseline

So, instead of using -textheight, you should be using FontMetrics#getAscent

Second, you need to keep track of all the previous locations text was painted, you could do this by using FontMetrics#getStringBounds, which returns a Rectangle2D and simply keep a list of this, which you can iterate over to check if the new text intersects any other, as an example...

Random words

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage img;

        public TestPane() {
            img = new BufferedImage(1200, 650, BufferedImage.TYPE_INT_ARGB);
            try (BufferedReader br = new BufferedReader(new FileReader(new File("Words.txt")))) {
                List<String> words = new ArrayList<>(25);
                String text = null;
                System.out.println("Read");
                while ((text = br.readLine()) != null) {
                    words.add(text);
                }
                System.out.println("Loaded " + words.size());

                Collections.sort(words);

                Random rnd = new Random();
                Font font = getFont();
                Graphics2D g2d = img.createGraphics();
                g2d.setColor(Color.WHITE);
                g2d.fillRect(0, 0, 1200, 650);

                List<Rectangle2D> used = new ArrayList<>(25);
                for (String word : words) {
                    int size = rnd.nextInt(37) + 11;
                    Font drawFont = font.deriveFont((float) size);
                    FontMetrics fm = g2d.getFontMetrics(drawFont);
                    Rectangle2D bounds = fm.getStringBounds(word, g2d);
                    System.out.println("Positing " + word);
                    do {
                        int xPos = rnd.nextInt(1200 - (int)bounds.getWidth());
                        int yPos = rnd.nextInt(650 - (int)bounds.getHeight());

                        bounds.setFrame(xPos, yPos, bounds.getWidth(), bounds.getHeight());
                    } while (collision(used, bounds));
                    used.add(bounds);
                    g2d.setFont(drawFont);
                    g2d.setColor(Color.BLACK);
                    g2d.drawString(word, (float)bounds.getX(), (float)bounds.getY() + fm.getAscent());
                    g2d.setColor(Color.RED);
                    g2d.draw(bounds);
                }
                g2d.dispose();
            } catch (IOException exp) {
                exp.printStackTrace();
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(1200, 650);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.drawImage(img, 0, 0, this);
            g2d.dispose();
        }

        protected boolean collision(List<Rectangle2D> used, Rectangle2D bounds) {
            boolean collides = false;
            for (Rectangle2D check : used) {
                if (bounds.intersects(check)) {
                    collides = true;
                    break;
                }
            }
            return collides;
        }

    }

}

The red rectangles are just for demonstration purposes and you can get rid of those.

This does, however, demonstrate a small problem, not all the text fills all of the rectangle.

A more complex solution would be to use a TextLayout to generate a Shape of the text, which would allow words to be grouped much closed together.

Have a look at Assigning a image to a String for a demonstration of how this can be generated

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1
  1. Get a Shape for the text as seen in this answer.
  2. Check if the shapes intersect as seen in this answer.
Community
  • 1
  • 1
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433