prmr / JetUML

A desktop application for fast UML diagramming.
GNU General Public License v3.0
625 stars 125 forks source link

Inconsistent FontMetrics Calculations #432

Open prmr opened 3 years ago

prmr commented 3 years ago

Description The boundaries of nodes can jump around when some of their text properties are edited.

This is caused by the value of FontMetrics#getDimension being inconsistent, namely, it returns different values for the same input string at different points in the execution of the program. This inconsistency seems to be caused by the bounds of the Text node used to compute the text dimensions being erratically recomputed within JavaFX. Debugging shows that the valid flag in LazyBoundsProperty#get is unpredictably true or false, leading to the correct value when it is false, and some slightly lower and incorrect value when it is true. For some reason, hitting "backspace" when editing a node invalidates the cached value. This is all very mysterious.

This appears to be Windows-specific, as the bug is not reproducible on MacOS.

Steps to reproduce

In user mode:

  1. Create new class diagram
  2. Create a new note node
  3. Add and remove characters over multiple lines, using backspace
  4. Notice the node bounds move around slightly.

In development mode:

  1. Add the following line in FontMetrics#getDimension, below the line Bounds bounds = aTextNode.getLayoutBounds();:

System.out.println(String.format("height=%.1f", bounds.getHeight()));

  1. Create new class diagram
  2. Create a new note node
  3. Type "XXXXXX" in the node's text property, notice a first value (16.0 in Windows with default fonts).
  4. Type backspace, notice a different value (17.0 in Windows)
  5. Type more "X" characters, notice the value eventually reverts to the initial one (16).

Configuration

Additional Information This issue is not mentioned in the relevant Stack Overflow post, and in fact running the basic example below does not reproduce the bug, so it's due to some deeper interaction.

public class Test extends Application {

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

    public void start(Stage stage) {
        TextField field = new TextField();
        field.setOnAction( e -> { 
            Text text = new Text(field.getText());
            System.out.println(text.getLayoutBounds()); 
        } );
        HBox hbox = new HBox(field);
        stage.setScene(new Scene(hbox));
        stage.show();
    }
}