On macOS Big Sur, running JDK 17 and JavaFX 18, when you have an irregular clipping region, the clipped image ends up badly pixelated when printed.
The pixelation happens both on actual paper (on my Epson printer), as well as when saved to a PDF (using macOSs innate "Save to PDF" feature).
If you have a clip region with a Rectangle, there is no pixelation, everything looks smooth and crisp.
Note, that it must be a Rectangle, a rectangular Polygon has problems as well.
This is a screen shot of the actual screen rendering:

Next is a screen shot of viewing the resulting PDF.
You can see both the text and lines are pixelated within the circle.
Seems to me that its rendering the node in to the "PDF resolution", which is 72 DPI (not actual printer resolution), and then masking the region using a bitmap operation rather than actually clipping the content to the irregular border. But it special cases the Rectangle and clips to that.
Is there anything that can be done about this?
Below is a sample demonstrating this.
package pkg;
import javafx.application.Application;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class App extends Application {
Pane printPane;
@Override
public void start(Stage stage) {
var scene = new Scene(getView(), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private Pane getView() {
var flowPane = new FlowPane(getCirclePane(), getRectPane());
var b = new Button("Print");
printPane = flowPane;
b.setOnAction((t) -> {
var job = PrinterJob.createPrinterJob();
if (job != null && job.showPrintDialog(null)) {
if (job.printPage(printPane)) {
job.endJob();
}
}
});
var bp = new BorderPane();
bp.setCenter(flowPane);
bp.setBottom(b);
return bp;
}
private Pane getCirclePane() {
var pane = new Pane();
var circle = new Circle(100, 100, 45);
pane.setClip(circle);
var text = new Text(75, 100, "Circle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
return pane;
}
private Pane getRectPane() {
var pane = new Pane();
var rect = new Rectangle(50, 50, 200, 125);
pane.setClip(rect);
var text = new Text(125, 100, "Rectangle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
return pane;
}
}
Update:
I came up with this as a workaround.
Simply, that which I can't clip, I can paint over.
I create a new rect the size of the overall scene, then use Shape.subtract(...) to punch a hole in it in the shape of my clip region. I fill this new shape with my background color. Then I put this in a new pane, and use a Stack pane to layer it on top of my master. It's imperfect, and a little bit fiddly, and it chafes having to do this. But it works and is easy to explain.
So, using the circle as an example:
private Pane getCirclePane() {
var pane = new Pane();
var circle = new Circle(100, 100, 45);
var text = new Text(75, 100, "Circle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
Rectangle rect = new Rectangle(0, 0, 250, 200);
Shape overlay = Shape.subtract(rect, circle);
overlay.setFill(Color.WHITE);
Pane overlayPane = new Pane();
overlayPane.getChildren().add(overlay);
pane = new StackPane(pane, overlayPane);
return pane;
}

