Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8194713

In multiline wrapped TextFlow, the node's getBaselineOffset is incorrect after the first line.

XMLWordPrintable

    • generic
    • generic

      FULL PRODUCT VERSION :
      java version "9.0.1"
      Java(TM) SE Runtime Environment (build 9.0.1+11)
      Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 10.0.16299.125]

      A DESCRIPTION OF THE PROBLEM :
      For a wrapped Textflow of several lines,containing Text words of different font sizes,it appears the baseline offset of any word is based on the words in line 1 of TextFlow, even though the following lines have different heights.

      Even though Text nodes were used in the test case, this problem will occur for any type of node where the node heights vary within the TextFlow.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      run the test case

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Expected different baseline offset for each node of wrapped TextFlow, depending on the line height where the node is located.
      ACTUAL -
      Same baseline offset for any node on any line of the wrapped TextFlow.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      //
      package tools;
      //
      import javafx.application.Application;
      import javafx.scene.Scene;
      import javafx.stage.Stage;
      import javafx.scene.Node;
      import java.util.logging.Level;
      import java.util.logging.Logger;
      import javafx.geometry.Bounds;
      import javafx.geometry.Insets;
      import javafx.geometry.Pos;
      import javafx.scene.shape.Rectangle;
      import javafx.scene.text.Font;
      import javafx.scene.text.FontWeight;
      import javafx.scene.text.Text;
      import javafx.scene.text.TextFlow;
      import javafx.scene.layout.VBox;
      import javafx.scene.layout.BorderPane;
      import javafx.scene.layout.StackPane;
      import javafx.scene.input.MouseEvent;
      import javafx.scene.input.MouseButton;
      import javafx.scene.paint.Color;
      import javafx.collections.ObservableMap;
      import javafx.event.EventHandler;
      //
      //
      public class TextBase extends Application {
      /*
      This app tests baseline offsets of Text words within a multiline
      TextFlow, in which a few words have bigger font sizes. In all cases
      it appears the baseline offset is based on the words in line 1 of
      TextFlow, even though the following lines have different heights.

      Is this a bug in TextFlow?
      */

      private static final Logger LOG = Logger.getLogger(TextBase.class.getName());
      //
      //init fonts
      private Font mainFont = Font.font("DejaVu Serif", FontWeight.NORMAL, 20);
      private Font bigFont = Font.font("DejaVu Serif", FontWeight.NORMAL, 48);
      //
      //init panes
      private BorderPane bp = new BorderPane();
      private StackPane sp = new StackPane();
      private VBox vbox = new VBox();
      private TextFlow flow = new TextFlow();
      //
      //init sample string of words
      private String s1 = "Electromagnetic waves cover a wide spectrum from radio waves to gamma rays. Waves can interfere. "
          + "Constructive interference occurs when two waves are in phase. Destructive interference occurs when two waves are "
          + "out of phase. When waves pass through a diffraction grating, they interfere and produce a pattern of bright and "
          + "dark spots on a screen.";
      //
      //comment out one of these bigIndexList declarations
      //first big font word in line 2
      //private int[] bigIndexList = {10, 17};
      //
      //first big font word in line 1
      private int[] bigIndexList = {1, 10, 17};

          /**
           * Default Constructor
           */
          public TextBase() {
          }

      public void test(){
      try{
      init();

      addText(flow, s1);

      vbox.getChildren().add(flow);


      } catch (Exception ex) {
      LOG.log(Level.SEVERE, ex.getMessage(), ex);
      throw new RuntimeException(ex);
      }
      }

      public void init(){
      try{
      //insets: top, right, bottom, left
      bp.setPadding(new Insets(5, 5, 5, 5));
      bp.setStyle(" -fx-border-color: green;-fx-border-width: 2px;-fx-border-style: solid;");
      bp.setPrefSize(500,400);
      //
      sp.setPadding(new Insets(10, 5, 10, 5));
      sp.setStyle(" -fx-border-color: blue;-fx-border-width: 1px;-fx-border-style: solid;");
      //
      vbox.setSpacing(8);
      vbox.setPadding(new Insets(15, 5, 15, 5));
      vbox.setStyle(" -fx-border-color: red;-fx-border-width: 1px;-fx-border-style: solid;");
      //
      } catch (Exception ex) {
      LOG.log(Level.SEVERE, ex.getMessage(), ex);
      throw new RuntimeException(ex);
      }
      }


      public void addText(TextFlow flow, String s){
      try{
      LOG.log(Level.INFO, "\n\nText s: " + s);
      //split string into words
      String[] ss = s.split(" ");
      int len = ss.length;
      int bigIndex = 0;
      int bigEnd = bigIndexList.length;
      for(int i = 0; i<len; i++){
      Text tx = new Text(ss[i] + " ");
      if(bigIndex < bigEnd && i == bigIndexList[bigIndex]){
      //set big font on a few words
      tx.setFont(bigFont);
      bigIndex++;
      } else {
      //set main font on most words
      tx.setFont(mainFont);
      }
      //add mouse click handler
      tx.setOnMouseReleased(new EventHandler<MouseEvent>() {
                      @Override
                      public void handle(MouseEvent event) {
                          if (event.getButton() == MouseButton.PRIMARY) {
                              event.consume();
                              int index = flow.getChildren().indexOf(tx);
                              //check baseline offset
                              checkBase(flow, index);
                          }
                      }
                  });
      //add one word to flow
      flow.getChildren().add(tx);
      }
      flow.setStyle(" -fx-border-color: orange;-fx-border-width: 1px;-fx-border-style: solid;");

      } catch (Exception ex) {
      LOG.log(Level.SEVERE, ex.getMessage(), ex);
      throw new RuntimeException(ex);
      }
      }

      public void checkBase(TextFlow flow, int index){
      //check baselines of selected nodes
      try{
      //
      Node n = flow.getChildren().get(index);
      Text t = (Text)n;

      //create rectangle around selected node
      addBox(n);

      //
      Bounds layoutB = n.getLayoutBounds();
      double minY = layoutB.getMinY();
      double layY = n.getLayoutY();
      // LOG.log(Level.INFO, "\n\n index: " + index + ", node layout bounds: " + layoutB);
      //
      double base = n.getBaselineOffset();
      LOG.log(Level.INFO, "\n index: " + index + ", text: " + t.getText() + ", minY:" + minY + ", layY:" + layY + ", node baseline offset: " + base );
      //
      } catch (Exception ex) {
      LOG.log(Level.SEVERE, ex.getMessage(), ex);
      throw new RuntimeException(ex);
      }
      }

      public void addBox(Node n){
      //add box around of selected node
      try{
      //
      //get text bounds within flow (parent)
      Bounds parentB = n.getBoundsInParent();
      LOG.log(Level.INFO, "\n\n node bounds in parent: " + parentB);

      //convert to scene coords
      Bounds sceneB = flow.localToScene(parentB);
      // LOG.log(Level.INFO, "\n\n scene bounds: " + sceneB);

      //create rectangle in scene coords
      Rectangle r = new Rectangle( sceneB.getMinX(), sceneB.getMinY(), sceneB.getWidth(), sceneB.getHeight() );
      r.setStroke(Color.GREEN);
      r.setFill(Color.TRANSPARENT);
      //add rectangle to border pane
      bp.getChildren().add(r);
      //
      } catch (Exception ex) {
      LOG.log(Level.SEVERE, ex.getMessage(), ex);
      throw new RuntimeException(ex);
      }
      }


          @Override
          public void start(Stage primaryStage) throws Exception{

      //add textflow to vbox
      test();
      // add stack pane to outer pane
      sp.setAlignment(Pos.CENTER);
      sp.getChildren().add(vbox);
      bp.setCenter(sp);
      //
              // setup scene and stage
              Scene scene = new Scene( bp, 600, 500 );

              primaryStage.setScene(scene);
              primaryStage.sizeToScene();
              primaryStage.show();
      //check baselines of selected nodes
          }



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

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: