How to calculate the pixel width of a String in JavaFX?
If you are just measuring the default font without CSS:
- Place the String to be measured in a Text object.
- Get the width of the Text object's layout bounds.
If you need to apply CSS:
- Place the String to be measured in a Text object.
- Create a throwaway Scene and place the Text object in the Scene.
- Take a snapshot of the Text (if you are using Java 7) or call applyCss for Java 8.
- 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):
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 isLOGICAL
, which bases the bounds on the metrics of the font, rather than the actual text being rendered. I think what you want here areVISUAL
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);
}
}
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:
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:
- Place the String to be measured in a Text object.
- Get the width of the Text object's layout bounds.
If you need to apply CSS:
- Place the String to be measured in a Text object.
- Create a throwaway Scene and place the Text object in the Scene.
- Take a snapshot of the Text (if you are using Java 7) or call applyCss for Java 8.
- 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):
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
How to Read Data from Xls (Excel) File [Java, Android]
Error: Gradle: Execution Failed for Task ':App:Predexdebug'
Android Retrofit Design Patterns
How to Declare on UI Component in Android with Kotlin
How to Combine One Android Studio Project into Another Android Studio Project
Boot_Completed Not Working on Android 10 Q API Level 29
Gson Expected Begin_Array But Was Begin_Object
R.Raw.Anything Cannot Be Resolved
How to Iterate Through the Id Properties of R.Java Class
Get Current Time in a Given Timezone:Android
Problems Importing Project into Android Studio Regarding Actionbarsherlock
How to Generate Multiple Lines in PDF Using Apache PDFbox
How to Use Flood Fill Algorithm in Android
How to Simulate Low Memory in the Android Emulator
Fatal Exception: Firebase-Messaging-Intent-Handle -- Java.Lang.Noclassdeffounderror