Search code examples
javafxcanvas

JavaFX Canvas TextFill Rotate


Being on Canvas, how to use textFill rotate with same behavior as "Node Text rotate".

In this illustrative example, on the left is the "Node Text rotate" working correctly, on the right is the fillText on the canvas working wrong.

I was looking for a solution and only the rotate method doesn't work, it was suggested to use the the translate method, but unfortunately I'm not able to leave it with the same behavior node rotate

Image

package com.datagenia.testa;

import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment; 
import javafx.stage.Stage;

public class TranslateSample2 extends Application {

    double rotate = 170;
    
    Text text = null;
    String word = "TextFill Rotate";
    
    Canvas canvas = new Canvas(300, 200);
    
    @Override
    public void start(Stage primaryStage) {
        
        Pane pane = new Pane();
 
        text = new Text(50, 100, word);
        text.setFont(javafx.scene.text.Font.font("Arial", 18));
        text.setFill(Color.BLACK);
        
        Text text2 = new Text(50, 100, word);
        text2.setFont(javafx.scene.text.Font.font("Arial", 18));         
        text2.setFill(Color.ORANGE);
        
        text.setRotate(rotate);
 
        paint();
         
        pane.getChildren().addAll(text2, text);

        HBox displays = new HBox(pane, canvas);
        
        TextField tf = new TextField( "170" );
        tf.setOnAction( e -> {
            rotate = Double.valueOf( tf.getText() );
            paint();
        });
        
        
        StackPane sp = new StackPane();
        
        BorderPane bp = new BorderPane();
        bp.setCenter(displays);
        bp.setBottom(tf);
        
         
        Scene scene = new Scene(bp, 600, 300);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Canvas TextFill Rotate");
        primaryStage.show();
        
        
    }
    
    
    public void paint() {
        
        text.setRotate(rotate);
  
        GraphicsContext gc = canvas.getGraphicsContext2D();
        
        gc.clearRect(0,0, 300, 200);
          
        gc.save();
        gc.setFont(text.getFont());
        gc.setFill(Color.ORANGE);
        gc.setTextBaseline(VPos.BASELINE);
        gc.fillText(word, 50, 100); // Desenha o word// Desenhando o word no Canvas
        gc.restore();
       
        
        gc.save();
        gc.setTextAlign(TextAlignment.CENTER);        
        gc.setTextBaseline(VPos.BASELINE);
        
        gc.translate(50.0 + (text.getLayoutBounds().getWidth() / 2.0), 100.0); 
        gc.rotate(rotate);  
        
        gc.setFill(Color.BLACK);
        gc.setFont(text.getFont());
        
        gc.fillText(word, 0, 0);         
        gc.restore();
    }
    
    
    public static void main(String[] args) {
        launch(args);
    }
}

Please, I'm not interested in a solution that saves the "Node Text" as an image rotated and then display it on the canvas


Solution

  • Node.setRotate(...) will rotate a node around its center, in the coordinate system of its layout bounds. So while it's not entirely clear what your actual requirements and constraints are, if you want to mimic that you need to arrange for the rotation in the canvas to be about the center of the text.

    The simplest solution is probably to center the text at the specified canvas coordinates, and then to apply a rotation with those coordinates as the pivot point. This avoids having to calculate the translation by hand, but of course relies on knowing the coordinates of the center of the text. Presumably, since you're using a canvas, you don't want to create a Text node and compute its layout bounds (the overhead in doing so would completely eliminate any benefits you get from using a Canvas in the first place), but since the original post included that, I've left it in here:

    
    import javafx.application.Application;
    import javafx.geometry.VPos;
    import javafx.scene.Scene;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Pane;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Color;
    import javafx.scene.text.Text;
    import javafx.scene.text.TextAlignment;
    import javafx.scene.transform.Affine;
    import javafx.scene.transform.Rotate;
    import javafx.stage.Stage;
    
    public class TranslateSample2 extends Application {
    
        double rotate = 170;
    
        Text text = null;
        String word = "TextFill Rotate";
    
        Canvas canvas = new Canvas(300, 200);
    
        @Override
        public void start(Stage primaryStage) {
    
            Pane pane = new Pane();
    
            text = new Text(50, 100, word);
            text.setFont(javafx.scene.text.Font.font("Arial", 18));
            text.setFill(Color.BLACK);
    
            Text text2 = new Text(50, 100, word);
            text2.setFont(javafx.scene.text.Font.font("Arial", 18));
            text2.setFill(Color.ORANGE);
    
            text.setRotate(rotate);
    
            paint();
    
            pane.getChildren().addAll(text2, text);
    
            HBox displays = new HBox(pane, canvas);
    
            TextField tf = new TextField( "170" );
            tf.setOnAction( e -> {
                rotate = Double.valueOf( tf.getText() );
                paint();
            });
    
    
            StackPane sp = new StackPane();
    
            BorderPane bp = new BorderPane();
            bp.setCenter(displays);
            bp.setBottom(tf);
    
    
            Scene scene = new Scene(bp, 600, 300);
            primaryStage.setScene(scene);
            primaryStage.setTitle("Canvas TextFill Rotate");
            primaryStage.show();
    
    
        }
    
    
        public void paint() {
    
            text.setRotate(rotate);
    
            GraphicsContext gc = canvas.getGraphicsContext2D();
    
            // x and y coordinates of the center of the text
            // In real life you probably don't want the overhead of computing the
            // layout bounds of a text node, which would eliminate all the benfits
            // of using a canvas in the first place.
            double x = text.getBoundsInLocal().getCenterX() ;
            double y = text.getBoundsInLocal().getCenterY() ;
    
            gc.clearRect(0,0, 300, 200);
            gc.setTextAlign(TextAlignment.CENTER);
            gc.setTextBaseline(VPos.CENTER);
    
            gc.save();
            gc.setFont(text.getFont());
            gc.setFill(Color.ORANGE);
            gc.fillText(word, x, y); // Desenha o word// Desenhando o word no Canvas
            gc.restore();
    
    
            gc.save();
    
            Rotate rotation = new Rotate(rotate, x, y);
            gc.setTransform(new Affine(rotation));
    
            gc.setFill(Color.BLACK);
            gc.setFont(text.getFont());
    
            gc.fillText(word, x, y);
            gc.restore();
        }
    
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    enter image description here