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

TextFlow caretShape method returns wrong coordinates when TextFlow has left inset.

XMLWordPrintable

    • generic
    • generic

      FULL PRODUCT VERSION :
      java version "10-ea" 2018-03-20
      Java(TM) SE Runtime Environment 18.3 (build 10-ea+37)
      Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10-ea+37, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 10.0.16299.125]

      A DESCRIPTION OF THE PROBLEM :
      TextFlow caretShape method returns wrong coordinates when TextFlow has left or top inset.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      See Case 1 in test code:
      Add some Text nodes to a TextFlow. For one of the Text nodes, call getBoundsInParent, and note the minX location. Now count what the character index is at the start of this Text node. Call caretShape at this index, and note the MoveTo x location. The two x locations are within 1 pixel, which is fine.

      See Case 2 in test code:
      Now add a left or top inset to the TextFlow, and the caretShape MoveTo x differs from the getBoundsInParent minX by the amount of the inset.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      After adding a left inset to the TextFlow, the caretShape MoveTo x should be
      within one pixel of the getBoundsInParent minX.
      ACTUAL -
      case 1 - no inset
      BoundingBox [minX:60.08203125
      MoveTo[x=59.08203125

      case 2 - left inset = 17
      BoundingBox [minX:131.408203125
      MoveTo[x=113.408203125

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      no error message

      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.text.Font;
      import javafx.scene.text.FontWeight;
      import javafx.scene.text.Text;
      import javafx.scene.shape.LineTo;
      import javafx.scene.shape.MoveTo;
      import javafx.scene.shape.PathElement;
      import javafx.scene.text.TextFlow;
      import javafx.scene.layout.VBox;
      import javafx.scene.layout.BorderPane;
      import javafx.scene.layout.StackPane;
      import javafx.scene.paint.Color;
      import javafx.collections.ObservableMap;
      import javafx.collections.ObservableList;
      import javafx.event.EventHandler;
      import javafx.scene.layout.Pane;
      import javafx.scene.layout.FlowPane;
      //
      //
      public class FlowCaret extends Application {
      /*
      This app tests the caretShape method in TextFlow. When TextFlow has a
      top or left inset, it appears the caretShape coordinates are off by
      the amount of the insets.

      Is this a bug in TextFlow?
      */

      private static final Logger LOG = Logger.getLogger(FlowCaret.class.getName());
      //
      //init fonts
      private Font mainFont = Font.font("DejaVu Serif", FontWeight.NORMAL, 20);
      //
      //init panes
      private BorderPane bp = new BorderPane();
      private StackPane sp = new StackPane();
      private VBox vbox = new VBox();
      //

      //
      //init sample string of words
      private String s1 = "Electromagnetic waves cover a wide spectrum from radio signals to gamma rays. Waves can interfere. "
          + "Constructive interference occurs when two waves are in phase. ";

      private String s2 = "Electromagnetic waves cover a wide spectrum from radio signals to gamma rays. Waves can interfere. "
          + "Constructive interference occurs when two waves are in phase. ";

      private String s3 = "Electromagnetic waves cover a wide spectrum from radio signals to gamma rays. Waves can interfere. "
          + "Constructive interference occurs when two waves are in phase. ";

      private String s4 = "Electromagnetic waves cover a wide spectrum from radio signals to gamma rays. Waves can interfere. "
          + "Constructive interference occurs when two waves are in phase. ";

      private TextFlow flow1;
      private TextFlow flow2;
      private TextFlow flow3;
      private TextFlow flow4;



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

      public void test(){
      try{
      initTest();
      //
      initFlows();

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

      public void initTest(){
      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 initFlows(){
      try{
      // case 1
      // no padding
      flow1 = addText(s1);
      flow1.setId("case 1");
      vbox.getChildren().add(flow1);
      //
      // case 2
      // add padding to textflow
      flow2 = addText(s2);
      flow2.setId("case 2");
      //insets: top, right, bottom, left
      flow2.setPadding(new Insets(13, 17, 10, 17));
      vbox.getChildren().add(flow2);

      // case 3
      // workaround, put textflow inside pane container
      // no padding
      flow3 = addText(s3);
      flow3.setId("case 3");
      StackPane p3 = new StackPane();
      p3.getChildren().add(flow3);
      vbox.getChildren().add(p3);
      //
      // case 4
      // workaround, put textflow inside pane container
      // add padding to pane container
      flow4 = addText(s4);
      flow4.setId("case 4");
      StackPane p4 = new StackPane();
      p4.getChildren().add(flow4);
      //insets: top, right, bottom, left
      p4.setPadding(new Insets(13, 17, 10, 17));
      vbox.getChildren().add(p4);


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

      public void check(){
      try{
      int nodeIndex = 8;
      checkCaret(flow1, nodeIndex);
      checkCaret(flow2, nodeIndex);
      checkCaret(flow3, nodeIndex);
      checkCaret(flow4, nodeIndex);
      } catch (Exception ex) {
      LOG.log(Level.SEVERE, ex.getMessage(), ex);
      throw new RuntimeException(ex);
      }

      }

      public TextFlow addText(String s){
      TextFlow flow = new TextFlow();
      try{
      LOG.log(Level.INFO, "\n\nText s: " + s);
      //split string into words
      String[] ss = s.split(" ");
      int len = ss.length;
      for(int i = 0; i<len; i++){
      //create a word
      Text tx = new Text(ss[i] + " ");
      //set font on each word
      tx.setFont(mainFont);
      //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);
      }
      return flow;
      }

      public void checkCaret(TextFlow flow, int nodeIndex){
      //check caret shape at selected node
      try{
      ObservableList<Node> wordList = flow.getChildren();
      Node textNode = wordList.get(nodeIndex);
      Text t = (Text)textNode;
      LOG.log(Level.INFO, "\n\n\n flow id: " + flow.getId() + ", text: " + t.getText());
      //
      //get char index at nodeIndex
      int charIndex = 0;
      for(int k = 0; k < nodeIndex; k++){
      Node n = wordList.get(k);
      int wordLen = ((Text)n).getText().length();
      //sum characters in each word
      charIndex += wordLen;
      }
      //
      Insets pad = flow.getPadding();
      LOG.log(Level.INFO, "\n flow id: " + flow.getId() + ", flow padding: " + pad );
      Bounds b = textNode.getBoundsInParent();
      LOG.log(Level.INFO, "\n charIndex: " + charIndex + ", bounds In Parent: " + b );
      //
      //get Text caret shape
      PathElement[] cshape = flow.caretShape(charIndex, true);
      //
      MoveTo moveto = (MoveTo)cshape[0];
      LineTo lineto = (LineTo)cshape[1];

      LOG.log(Level.INFO, "\n index: " + charIndex + ", moveto: " + moveto + ", lineto: " + lineto);
      //
      } 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();

          }



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

      CUSTOMER SUBMITTED WORKAROUND :
      Test code shows a workaround in case 3 and 4, in which each TextFlow is added to a StackPane before adding to the VBox. Rather than adding an inset to a TextFlow, add the inset to the TextFlow container.

      Caes 3 and 4 - TextFlow inset = 0.
      Case 3 - Stackpane left inset = 0, and the x locations match as in Case1.
      Case 4 - Stackpane left inset =17, and the x locations match as in Case1.

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

              Created:
              Updated: