How to Calculate the Pixel Width of a String in Javafx

How to calculate the pixel width of a String in JavaFX?

If you are just measuring the default font without CSS:

  1. Place the String to be measured in a Text object.
  2. Get the width of the Text object's layout bounds.

If you need to apply CSS:

  1. Place the String to be measured in a Text object.
  2. Create a throwaway Scene and place the Text object in the Scene.
  3. Take a snapshot of the Text (if you are using Java 7) or call applyCss for Java 8.
  4. Get the width of the Text object's layout bounds.

This works because it forces a layout pass on the Text which calculates it's layout bounds.
The scene in step 2 is required because that is just the way the CSS processor works (it needs a node to be located in a Scene to be able to do its job). Definitely read the linked javadoc for applyCss if you want to understand the processing further.

Sample Code

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;

// displays the width in pixels of an arbitrary piece of text.
public class MeasureText extends Application {
public static void main(String[] args) { launch(args); }
@Override public void start(Stage stage) throws Exception {
final Text text = new Text("XYZZY");
new Scene(new Group(text));

// java 7 =>
// text.snapshot(null, null);
// java 8 =>
text.applyCss();

final double width = text.getLayoutBounds().getWidth();

stage.setScene(new Scene(new Label(Double.toString(width))));
stage.show();
}
}

Sample program output (displays the width in pixels of an arbitrary piece of text):

Sample Program Output

How (if at all) would this change if the text was printed to a graphicscontext with a set font?

Apply the font to a text object containing the same message you will plot to the canvas. Unlike when you are measuring text plotted to the scene graph, items plotted to a canvas do not have CSS applied to them, so you don't need to place the Text object in a scene and have CSS applied to it before measuring the text. You can measure the layout bounds of your text object and it will be the same as the bounds of the text plotted within the canvas with the same font.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.*;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.text.*;
import javafx.stage.Stage;

// displays the width in pixels of an arbitrary piece of text (which has been plotted on a canvas).
public class MeasureText extends Application {
@Override
public void start(Stage stage) throws Exception {
final String msg = "XYZZY";
final Text text = new Text(msg);
Font font = Font.font("Arial", 20);
text.setFont(font);

final double width = text.getLayoutBounds().getWidth();

Canvas canvas = new Canvas(200, 50);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFont(font);
gc.fillText(msg, 0, 40);

stage.setScene(new Scene(
new VBox(new Label(Double.toString(width)), canvas))
);
stage.show();
}

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

Calculate the display width of a string in Java

If you just want to use AWT, then use Graphics.getFontMetrics (optionally specifying the font, for a non-default one) to get a FontMetrics and then FontMetrics.stringWidth to find the width for the specified string.

For example, if you have a Graphics variable called g, you'd use:

int width = g.getFontMetrics().stringWidth(text);

For other toolkits, you'll need to give us more information - it's always going to be toolkit-dependent.

Correct setting of the text size in pixels

As pointed out in a comment (with a link to a truly excellent discussion of font metrics), the size of a font is not the height of its capital letters.

Probably (I would be interested to see other suggestions) the best way to achieve what you want is to define a custom Region to hold your text node, which overrides layoutChildren() so that it adjusts the font size so that the height fits the size of the region.

A couple of implementation notes:

  • Text.setBoundsType() determines how the bounds of the text are measured. The default is LOGICAL, which bases the bounds on the metrics of the font, rather than the actual text being rendered. I think what you want here are VISUAL bounds.
  • This implementation is content-biased to vertical; meaning that its width depends on its height. Once the height is known, the font size is computed and the preferred width is calculated as the width of the text.

This is somewhat limited (no multiline text, etc.) but should give you a starting point if you want something more sophisticated.

public class TextPane extends Region {
private final Text text ;
public TextPane(Text text) {
this.text = text ;
getChildren().add(text);
}

@Override
protected void layoutChildren() {
adjustFontSize(getHeight());
text.setY(getHeight());
text.setX(0);
}

private void adjustFontSize(double height) {
double textHeight = text.getBoundsInLocal().getHeight();
if (Math.abs(height - textHeight) > 1) {
Font currentFont = text.getFont() ;
double fontSize = currentFont.getSize() ;
text.setFont(Font.font(currentFont.getFamily(), height * fontSize / textHeight));
}
}

@Override
protected double computePrefWidth(double height) {
adjustFontSize(height);
return text.getBoundsInLocal().getWidth();
}

@Override
public Orientation getContentBias() {
return Orientation.VERTICAL;
}
}

Here's an example which basically implements what you were looking to do, by fixing the height of the text pane to 100:

public class TextHeight extends javafx.application.Application  {

@Override
public void start(Stage stage) {
Text text = new Text("EXAMPLE");
text.setBoundsType(TextBoundsType.VISUAL);
text.setTextOrigin(VPos.BOTTOM);
text.setFill(Color.BLUE);
TextPane textPane = new TextPane(text);
textPane.setMinHeight(100);
textPane.setMaxHeight(100);
Pane root = new Pane(textPane, new Rectangle(620, 0, 10, 100));
stage.setScene(new Scene(root, 700, 120));
stage.show();
}

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

Sample Image

If the text has glyphs with descent (e.g. if you change "EXAMPLE" to "Example", so the p descends below the baseline), the total height of the text will include the descent:

Sample Image

And here's an example which uses the text pane as the root node (so it's always the same size as the scene). Changing the window size will result in the text automatically updating to vertically fill the scene:

public class TextHeight extends javafx.application.Application  {

@Override
public void start(Stage stage) {
Text text = new Text("Example");
text.setBoundsType(TextBoundsType.VISUAL);
text.setTextOrigin(VPos.BOTTOM);
text.setFill(Color.BLUE);
TextPane textPane = new TextPane(text);
textPane.setPrefHeight(100);
stage.setScene(new Scene(textPane));
stage.show();
}

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

How do I calculate the width of a string in pixels?

There are a number of ways to achieve what you want, based on what it is you want to achieve, for example...

BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
FontMetrics fm = g2d.getFontMetrics();
System.out.println(fm.stringWidth("This is a simple test"));
g2d.dispose();

But this only has relevence for the BufferedImage and it's Graphics context, it will not translate back to say, something like a screen or printer.

However, so long as you have a Graphics context, you can achieve the same result.

This example, obviously, uses the default font installed for the Graphics context, which you can change if you need to...

How can I get the width and the height of a JavaFX Label?

The reason for returning the "old" size that the label actually doesn't get updated on the GUI in the moment when you set the textProperty of it, therefore the size is not changed.

You can listen to the widthProperty and heightProperty of the Label and resize the Rectangle in the listener:

speedLabel.widthProperty().addListener((obs, oldVal, newVal) -> {
backgroundLabel.setWidth(newVal.doubleValue());
});

speedLabel.heightProperty().addListener((obs, oldVal, newVal) -> {
backgroundLabel.setHeight(newVal.doubleValue());
});

or simply use bindings between the properties:

backgroundLabel.heightProperty().bind(speedLabel.heightProperty());
backgroundLabel.widthProperty().bind(speedLabel.widthProperty());

But if you just want to achieve a Label with some background, you don't actually need a Rectangle, just some CSS - you can check this question: FXML StackPane doesn't align children correctly

How to calculate the pixel width of a String in JavaFX?

If you are just measuring the default font without CSS:

  1. Place the String to be measured in a Text object.
  2. Get the width of the Text object's layout bounds.

If you need to apply CSS:

  1. Place the String to be measured in a Text object.
  2. Create a throwaway Scene and place the Text object in the Scene.
  3. Take a snapshot of the Text (if you are using Java 7) or call applyCss for Java 8.
  4. Get the width of the Text object's layout bounds.

This works because it forces a layout pass on the Text which calculates it's layout bounds.
The scene in step 2 is required because that is just the way the CSS processor works (it needs a node to be located in a Scene to be able to do its job). Definitely read the linked javadoc for applyCss if you want to understand the processing further.

Sample Code

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;

// displays the width in pixels of an arbitrary piece of text.
public class MeasureText extends Application {
public static void main(String[] args) { launch(args); }
@Override public void start(Stage stage) throws Exception {
final Text text = new Text("XYZZY");
new Scene(new Group(text));

// java 7 =>
// text.snapshot(null, null);
// java 8 =>
text.applyCss();

final double width = text.getLayoutBounds().getWidth();

stage.setScene(new Scene(new Label(Double.toString(width))));
stage.show();
}
}

Sample program output (displays the width in pixels of an arbitrary piece of text):

Sample Program Output

How (if at all) would this change if the text was printed to a graphicscontext with a set font?

Apply the font to a text object containing the same message you will plot to the canvas. Unlike when you are measuring text plotted to the scene graph, items plotted to a canvas do not have CSS applied to them, so you don't need to place the Text object in a scene and have CSS applied to it before measuring the text. You can measure the layout bounds of your text object and it will be the same as the bounds of the text plotted within the canvas with the same font.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.*;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.text.*;
import javafx.stage.Stage;

// displays the width in pixels of an arbitrary piece of text (which has been plotted on a canvas).
public class MeasureText extends Application {
@Override
public void start(Stage stage) throws Exception {
final String msg = "XYZZY";
final Text text = new Text(msg);
Font font = Font.font("Arial", 20);
text.setFont(font);

final double width = text.getLayoutBounds().getWidth();

Canvas canvas = new Canvas(200, 50);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFont(font);
gc.fillText(msg, 0, 40);

stage.setScene(new Scene(
new VBox(new Label(Double.toString(width)), canvas))
);
stage.show();
}

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


Related Topics



Leave a reply



Submit