Java Center Text in Rectangle

How can I center Graphics.drawString() in Java?

I used the answer on this question.

The code I used looks something like this:

/**
* Draw a String centered in the middle of a Rectangle.
*
* @param g The Graphics instance.
* @param text The String to draw.
* @param rect The Rectangle to center the text in.
*/
public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) {
// Get the FontMetrics
FontMetrics metrics = g.getFontMetrics(font);
// Determine the X coordinate for the text
int x = rect.x + (rect.width - metrics.stringWidth(text)) / 2;
// Determine the Y coordinate for the text (note we add the ascent, as in java 2d 0 is top of the screen)
int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent();
// Set the font
g.setFont(font);
// Draw the String
g.drawString(text, x, y);
}

IN Java Graphics2D, how can text be center aligned on a Rectangle?

FontMetrics#stringwidth will, generally, return the amount of pixels required to render the text

In the box

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
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(new Rectangle(150, 150, 50, 50)));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

public class TestPane extends JPanel {

private Rectangle boxIn;

public TestPane(Rectangle boxIn) {
this.boxIn = boxIn;
}

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

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
String text = "Hello";
FontMetrics fm = g2d.getFontMetrics();

int x = boxIn.x + ((boxIn.width - fm.stringWidth(text)) / 2);
int y = boxIn.y + (((boxIn.height - fm.getHeight()) / 2) + fm.getAscent());

g2d.setColor(Color.BLUE);
g2d.fill(boxIn);
g2d.setColor(Color.WHITE);
g2d.drawString(text, x, y);

g2d.dispose();
}

}

}

You can also use TextLayout, which is a little more complictated, but provides you with a bounding box, representing the area that would required to render the text. This is more useful for attributed text, but can still be useful

How to center text without using any layout?

This could all be accomplished much more easily by using a StackPane (e.g. as shown in @Oboe's answer). Using the appropriate layout should always be your first choice. However, the rest of my answer shows how to do it manually.


The x and y properties define the origin point of the text node. The x-coordinate determines where the left side of the text starts. However, the y-coordinate can be customized somewhat via the textOrigin property.

                   X (always)
|
Y (VPos.TOP)-------╔════════════════════════════════╗
║ ║
Y (VPos.CENTER)----║ Text Node ║
║ ║
Y (VPos.BOTTOM)----╚════════════════════════════════╝

Note: There's also VPos.BASELINE (the default) but I don't know how to visualize that.

As you can see, when you set the x property to rectangle.getWidth() / 2 you are aligning the left side of the text node with the horizontal center of the rectangle. If you want to align the horizontal center of the text node with the horizontal center of the rectangle you have to take the width of the text node into account. Here's an example:

import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;

public class App extends Application {

@Override
public void start(Stage stage) {
var rectangle = new Rectangle(600, 100, Color.TURQUOISE);

var text = new Text("TEXT");
text.setBoundsType(TextBoundsType.VISUAL);
text.setFont(new Font(100));
text.setTextAlignment(TextAlignment.CENTER);
text.setTextOrigin(VPos.CENTER);

// WARNING: Assumes Rectangle is at (0, 0)
text.setX(rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2);
text.setY(rectangle.getHeight() / 2);

stage.setScene(new Scene(new Pane(rectangle, text)));
stage.show();
}
}

Notice that I set the x property after setting everything else (e.g. font, text, etc.). That's necessary for the resulting text's width to be known. Of course, this is not responsive; if the the dimensions of the rectangle or text change you have to manually recompute the new x value. The ideal solution, when we ignore layouts, is to use a bindings. Something like:

// WARNING: Assumes Rectangle is at (0, 0)
text.xProperty()
.bind(
Bindings.createDoubleBinding(
() -> rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2,
rectangle.widthProperty(),
text.layoutBoundsProperty()));

Unfortunately, changing the x property changes the layout bounds of the text and the above leads to a StackOverflowError. The same is true of the text's bounds-in-local (this is a characteristic of shapes). There is a solution though if you don't mind positioning the text via its layoutX and layoutY properties:

// WARNING: Assumes Rectangle is at (0, 0)

// leave text.x = 0 and text.y = 0
text.layoutXProperty()
.bind(
Bindings.createDoubleBinding(
() -> rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2,
rectangle.widthProperty(),
text.layoutBoundsProperty()));
// assumes textOrigin = VPos.CENTER
text.layoutYProperty().bind(rectangle.heightProperty().divide(2));

Note the layoutX and layoutY properties come from Node. They're used by layouts to position their children. But since you're not using a layout which automatically positions its children you can safely set them manually. There's also the translateX and translateY properties. These add onto the layout properties. For example, ignoring other things, the final x-position will be layoutX + translateX (similar for the final y-position); for a Text node the final x-position would be x + layoutX + translateX (again, similar for the final y-position).

You may have noticed that all my examples warn about assuming the rectangle is at the origin of its parent. To fix that issue you simply have to add the rectangles x and y positions to the text's layout position.

Make string centered in rectangle

If you want to center the text then you need to know the length of the text so you know its width relative to the width of the rectangle. This is done by getting the FontMetrics instance from the Graphics object.

So the basic code would be:

FontMetrics fm = g.getFontMetrics();
int stringWidth = fm.getStringWidth(...);
int xDiff = (width - stringWidth) / 2;
g.drawString(str, x + xDiff, ...);

Of course you will also need to center based on the height.

How to center a text in a rectangle in JavaFX

As @James_D comments, you can use the GraphicsContext methods setTextAlign() and setTextBaseline() to center the fillText() in an arbitrary Rectangle. Starting from this example, I added the following lines in the tooltips loop in order to produce the image shown:

gc.setTextAlign(TextAlignment.CENTER);
gc.setTextBaseline(VPos.CENTER);
gc.setFill(Color.BLACK);
gc.fillText(color.toString(),
bounds.getX() + bounds.getWidth() / 2,
bounds.getY() + bounds.getHeight() / 2);

image

Correct text position center in rectangle iText

I'm not sure if I understand your question correctly. I'm assuming you want to fit some text into a rectangle vertically, but I don't understand how you calculate the font size, and I don't see you setting the leading anywhere (which you can avoid by using ColumnText.showAligned()).

I've created an example named FitTextInRectangle which results in the PDF chunk_in_rectangle.pdf. Due to rounding factors (we're working with float values), the word test slightly exceeds the rectangle, but the code shows how to calculate a font size that makes the text fit more or less inside the rectangle.

In your code samples, the baseline is defined by the leading when using ColumnText (and the leading is wrong) or the bottom coordinate of the rectangle when using showText() (and you forgot to take into account value of the descender).

Java - How to visually center a specific string (not just a font) in a rectangle

While I muck about with TextLayout, you could just use the Graphics context's FontMetrics, for example...

Text

import java.awt.BorderLayout;
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.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

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

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

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

public class TestPane extends JPanel {

private String text;

public TestPane() {
text = "Along time ago, in a galaxy, far, far away";
}

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

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();

g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

Font font = new Font("Arial", Font.BOLD, 48);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int x = ((getWidth() - fm.stringWidth(text)) / 2);
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();

g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);

g2d.dispose();
}
}

}

Okay, after some fussing about...

Basically, text rendering occurs at the baseline, this makes the y position of the bounds usually appear above this point, making it look like the text is been painted above the y position

To overcome this, we need to add the font's ascent minus the font's descent to the y position...

For example...

FontRenderContext context = g2d.getFontRenderContext();
Font font = new Font("Arial", Font.BOLD, 48);
TextLayout txt = new TextLayout(text, font, context);

Rectangle2D bounds = txt.getBounds();
int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
y += txt.getAscent() - txt.getDescent();

... This is why I love rendering text by hand ...

Runnable example...

Layout

import java.awt.BorderLayout;
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.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

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

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

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

public class TestPane extends JPanel {

private String text;

public TestPane() {
text = "Along time ago, in a galaxy, far, far away";
}

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

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();

g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

FontRenderContext context = g2d.getFontRenderContext();
Font font = new Font("Arial", Font.BOLD, 48);
TextLayout txt = new TextLayout(text, font, context);

Rectangle2D bounds = txt.getBounds();
int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
y += txt.getAscent() - txt.getDescent();

g2d.setFont(font);
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);

g2d.setColor(Color.BLUE);
g2d.translate(x, y);
g2d.draw(bounds);

g2d.dispose();
}
}

}

Take a look at Working with Text APIs for more information...

Updated

As has already been suggested, you could use a GlyphVector...

Each word (Cat and Dog) is calculated separatly to demonstrate the differences

CatDog

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

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

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

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

public class TestPane extends JPanel {

private String text;

public TestPane() {
text = "A long time ago, in a galaxy, far, far away";
}

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

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();

g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

Font font = new Font("Arial", Font.BOLD, 48);
g2d.setFont(font);

FontRenderContext frc = g2d.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, "Cat");
Rectangle2D box = gv.getVisualBounds();

int x = 0;
int y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
g2d.drawString("Cat", x, y);

x += box.getWidth();

gv = font.createGlyphVector(frc, "Dog");
box = gv.getVisualBounds();

y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
g2d.drawString("Dog", x, y);

g2d.dispose();
}
}

}

I can't center text in java swing

This is close, you need to increase x by half the width and then reduce by half the string width. You also need to set the font before you get the font metrics otherwise you're getting the metrics of the existing Graphics font.

g.setFont(font);

FontMetrics fm = g.getFontMetrics();

int textX = x + (width / 2) - (fm.stringWidth(text) / 2);

int textY = y + ((height - fm.getHeight()) / 2) + fm.getAscent();

g.setColor(Color.white);

g.drawString(text, textX, textY);


Related Topics



Leave a reply



Submit