I have two TextFlow and in I need one vertical line across them (thanks to James_D who helped me with it. However, I need a line with width 1px
, but it seems that line is smoothed, so the rendered line width is 2px
This is my code:
public class Test extends Application {
@Override
public void start(Stage primaryStage) throws IOException {
var textFlow1 = createTextFlow();
textFlow1.setStyle("-fx-background-color: cyan");
var textFlow2 = createTextFlow();
textFlow2.setStyle("-fx-background-color: yellow");
VBox vbox = new VBox(textFlow1, textFlow2);
Scene scene = new Scene(vbox, 300, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
private TextFlow createTextFlow() {
Text textA = new Text("ABC ");
textA.setStyle("-fx-font-family: 'monospace'; -fx-font-size: 14;");
Line line = new Line();
line.setManaged(false);
line.setSmooth(false);
Text textB = new Text(" DEF");
textB.setStyle("-fx-font-family: 'monospace'; -fx-font-size: 14;");
TextFlow textFlow = new TextFlow(textA, line, textB) {
@Override
protected void layoutChildren() {
super.layoutChildren();
double x = textB.getBoundsInParent().getMinX();
line.setStartX(x);
line.setEndX(x);
line.setEndY(getHeight());
}
};
return textFlow;
}
public static void main(String[] args) {
launch(args);
}
}
And this is the result:
Could anyone say how to disable this smoothing for the line?
To get a crisp line, round the endpoint coordinates and modify them by half the stroke width. As your stroke is 1 pixel, adjust by 0.5.
For example:
double x = Math.round(textB.getBoundsInParent().getMinX()) - 0.5;
line.setStartX(x);
line.setEndX(x);
Taking a snapshot and zooming in the sample output:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
import java.io.IOException;
public class Test extends Application {
@Override
public void start(Stage primaryStage) throws IOException {
var textFlow1 = createTextFlow();
textFlow1.setStyle("-fx-background-color: cyan");
var textFlow2 = createTextFlow();
textFlow2.setStyle("-fx-background-color: yellow");
VBox vbox = new VBox(textFlow1, textFlow2);
vbox.setPadding(new Insets(5));
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
private TextFlow createTextFlow() {
Text textA = new Text("ABC ");
textA.setStyle("-fx-font-family: 'monospace'; -fx-font-size: 14;");
Line line = new Line();
line.setManaged(false);
Text textB = new Text(" DEF");
textB.setStyle("-fx-font-family: 'monospace'; -fx-font-size: 14;");
return new TextFlow(textA, line, textB) {
@Override
protected void layoutChildren() {
super.layoutChildren();
double x = Math.round(textB.getBoundsInParent().getMinX()) - 0.5;
line.setStartX(x);
line.setEndX(x);
line.setStartY(0.5);
line.setEndY(Math.round(getHeight()) - 0.5);
}
};
}
public static void main(String[] args) {
launch(args);
}
}
For further info see the section titled "Interaction with coordinate systems" for Shape
.
Most nodes tend to have only integer translations applied to them and quite often they are defined using integer coordinates as well. For this common case, fills of shapes with straight line edges tend to be crisp since they line up with the cracks between pixels that fall on integer device coordinates and thus tend to naturally cover entire pixels. On the other hand, stroking those same shapes can often lead to fuzzy outlines because the default stroking attributes specify both that the default stroke width is 1.0 coordinates which often maps to exactly 1 device pixel and also that the stroke should straddle the border of the shape, falling half on either side of the border. Since the borders in many common shapes tend to fall directly on integer coordinates and those integer coordinates often map precisely to integer device locations, the borders tend to result in 50% coverage over the pixel rows and columns on either side of the border of the shape rather than 100% coverage on one or the other. Thus, fills may typically be crisp, but strokes are often fuzzy.
Two common solutions to avoid these fuzzy outlines are to use wider strokes that cover more pixels completely - typically a stroke width of 2.0 will achieve this if there are no scale transforms in effect - or to specify either the StrokeType.INSIDE or StrokeType.OUTSIDE stroke styles - which will bias the default single unit stroke onto one of the full pixel rows or columns just inside or outside the border of the shape.
For a line, unlike a filled shape, adjusting StrokeType
, will not give you the desired result as a line has no width, only a length and position. StrokeType.INSIDE
for a line will result in the line not being displayed, and StrokeType.OUTSIDE
will result in a line twice the stroke width wide.
For those reasons, I adjust the coordinates for the line start and end positions, assuming the default StrokeType.CENTERED
rather than adjusting the stroke type. This makes the line stroke become centered on the center of the pixels the line is traversing, and, as the stroke is one pixel wide and the line is straight vertical, the line completely covers the pixels it traverses and no others.
As noted by Stefman1987 in comments:
Another solution is to use Rectangle instead of Line with width 1px - it works without any "fuzziness"
This matches up with the potential solution provided in the Javadoc comment quoted earlier:
fills of shapes with straight line edges tend to be crisp since they line up with the cracks between pixels that fall on integer device coordinates and thus tend to naturally cover entire pixels