'How to dynamically change font size in UI to always be the same width in JavaFX?

What I am trying to do is create a label in fxml, using Scenebuilder, which updates its font size to always ensure that the content of the label is the same size.

Some background info is that I am using an AnchorPane, which is maximized and non-resizable.

I do not need the height of the text to be the same--just the width. Also, I would only like to resize if it is too big to fit. So if the label is only 1 letter, I do not want it to be a giant single letter. I only have rudimentary ideas, of which some pseudocode is below. Thanks!

lengthOfLabel = menuLabel.getText().length();

if(lengthOfLabel > numOfCharsThatCanFitInWidth){
    menuLabel.setStyle("-fx-font-size: " + (int) (someConstant/lengthOfLabel) + ";")
}


Solution 1:[1]

You can use temp Text object to measure text size, and scale the font if it doesn't fit. Something like this:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Main extends Application {

    //maximum width of the text/label
    private final double MAX_TEXT_WIDTH = 400;
    //default (nonscaled) font size of the text/label
    private final double defaultFontSize = 32;
    private final Font defaultFont = Font.font(defaultFontSize);

    @Override
    public void start(Stage primaryStage) {

        final TextField tf = new TextField("Label text goes here");

        final Label lbl = new Label();
        lbl.setFont(defaultFont);
        lbl.textProperty().addListener((observable, oldValue, newValue) -> {
            //create temp Text object with the same text as the label
            //and measure its width using default label font size
            Text tmpText = new Text(newValue);
            tmpText.setFont(defaultFont);

            double textWidth = tmpText.getLayoutBounds().getWidth();

            //check if text width is smaller than maximum width allowed
            if (textWidth <= MAX_TEXT_WIDTH) {
                lbl.setFont(defaultFont);
            } else {
                //and if it isn't, calculate new font size,
                // so that label text width matches MAX_TEXT_WIDTH
                double newFontSize = defaultFontSize * MAX_TEXT_WIDTH / textWidth;
                lbl.setFont(Font.font(defaultFont.getFamily(), newFontSize));
            }

        });
        lbl.textProperty().bind(tf.textProperty());

        final AnchorPane root = new AnchorPane(lbl, tf);
        AnchorPane.setLeftAnchor(tf, 0d);
        AnchorPane.setRightAnchor(tf, 0d);
        AnchorPane.setBottomAnchor(tf, 0d);


        primaryStage.setScene(new Scene(root, MAX_TEXT_WIDTH, 200));
        primaryStage.show();
    }
}

Note that tmpText.getLayoutBounds() returns the bounds that do not include any transformations/effects (if these are needed, you'll have to add text object to temp scene and calculate its bounds in parent).

Text fits Text fits, again Text scaled down

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Guest 21