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.