JavaFX複数ページの印刷
JavaFXで描画した線をポスター印刷するサンプルプログラムです。
私の作っているフリーソフト、洋裁CADのために書いたコードを抜粋して紹介します。
SwingとFXの印刷解像度
元々
Java
楽天 Swingで記述したものなのですが、Swingでは印刷解像度が72DPI固定で、線がギザギザになってしまいます。
JavaFX楽天 は解像度をしていできないものの、Swing以上の解像度が可能なので、印刷部分だけをFXに差し替えました。
下の写真左側がSwing、右がFXで出力したものです。差は歴然としていますね。
線の描画
このページで紹介する印刷は、画像等は含まず、すべて線になります(CADの出力だから)。
線はすべてPolyLineで描き、そのあと移動します。
double[] d = getDouble( p );
polyline = new Polyline( d );
pane.getChildren().add( polyline );
polyline.setTranslateX( polyline.getLayoutBounds().getMinX() );
polyline.setTranslateY( polyline.getLayoutBounds().getMinY() );
図形(Shape)の描画クラスは他にもたくさんありますが、閉じた図形の中が塗りつぶされてしまうので、PolyLineだけを使うようにしました。
PolyLineは直線の集合なので、個々の線を短くすれば任意の曲線も描くことができます。
StackPaneに描く
線はStackPaneに描きます。
Paneは他にもいろいろありますが、線を描く場合、重ね合わせて描くこともあるので、StackPane以外ではPolyLineが並んでしまい、描きたい位置に置くことができません。
Fxでは他にCanvasクラスが描画用に用意されていますが、これは印刷には向きません。(出力が粗いため)
この時、StackPaneは左上を原点に設定しておきます。
stackPane.setAlignment( Pos.TOP_LEFT);
印刷スケール
StackPane上で100の長さの直線を引いた時、この線を100mmの長さで印刷したい場合は、下記係数を掛けます。
//ドットからプリンターへ正しいサイズで出力するための変換値
private double dtop = 2. *Math.sqrt(2);
これで等倍の印刷ができます。1/2にしたい場合はさらに0.5を掛ければOK。
サンプルプログラムの説明
3つのクラスに分かれています。
PrintStackPaneStage
ウインドを開き、印刷するStackPaneを表示する。ボタンをクリックすると印刷
PrintStackPane
PrintStackPaneStageから受け取ったStackPaneを印刷する
StackPaneにPolyLineを描画する
PrintStackPaneStageを開くと、setSize()メソッドで用紙サイズと縦横の枚数、糊代の幅を元に印刷範囲の大きさを設定します。
getNodeToPrint()は印刷するStackPaneを作るメソッド。枠線を引き、適当な線を描いています。枠線は印刷後に貼り合わせるための目印です。
印刷ボタン楽天 を押すと、PrintStackPaneに印刷のためのデータを渡し、印刷指示を出します。
PrintStackPaneは受け取ったStackPaneと印刷サイズ、印刷範囲を元に印刷を実行します。 基本的に印刷用紙より大きいものをポスター印刷する(印刷範囲より用紙の方が小さい)前提なので、印刷範囲の左上から下、右の順で出力します。
サンプルコード
PrintStackPaneStage
package tomojavalib.fx;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.print.PrinterJob;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import tomojavalib.p2cad.DrawOnPane;
import tomojavalib.p2cad.Point;
/**
* StackPaneを印刷するためのアプリケーション
* */
public class PrintStackPaneStage extends Application {
//印刷範囲を入れる四角形
private Rectangle printRectangle;
//プリンタージョブ
private PrinterJob job;
//用紙の情報
private String size ="A4横";
//余白
private double yohaku = 10.;
//用紙の幅と高さ
private double yousiW = 297;
private double yousiH = 210;
//縦と横の枚数
private int tatemaisu =1;
private int yokomaisu =1;
//ドットからプリンターへ正しいサイズで出力するための変換値
private double dtop = 2. *Math.sqrt(2);
//メイン関数
public static void main(String[] args) { launch(args); }
BorderPane root=null;
@Override
public void start(Stage primaryStage) {
//プリンタージョブの初期化
job = PrinterJob.createPrinterJob();
//紙のサイズと並べる枚数、余白を設定する
//サイズ、横枚数、縦枚数、余白
this.setSize("A4横" , 2 , 2 ,10);
//表示パネルの設定
root = new BorderPane();
Group pane = new Group();
//印刷対象を表示
pane.getChildren().add(getNodeToPrint());
//印刷範囲を表示
pane.getChildren().add(getPrintRectangle());
//プリントのためのボタン
Button printButton = new Button("Print!");
printButton.setOnAction(this::print);
//Paneとボタンを設置
root.setTop(new ToolBar(printButton));
root.setCenter(pane);
//ウインドウの設定
Scene scene = new Scene(root, 1000, 1200, Color.GRAY);
primaryStage.setScene(scene);
primaryStage.show();
}
/**ボタンを押したときの処理
* 印刷を実行する
* */
private void print(final ActionEvent actionEvent) {
//印刷実行classを初期化
PrintStackPane printer = new PrintStackPane();
//ページレイアウトに必要な情報を渡す
printer.setPage( size , yohaku * dtop , getPrintRectangle() );
//印刷実行
boolean success = printer.print( job , true , getNodeToPrint() );
if (success) { job.endJob(); }
return;
}
/**印刷範囲を示す四角形を作成する*/
private Rectangle getPrintRectangle() {
if (printRectangle == null) {
printRectangle = new Rectangle(yousiW*yokomaisu * dtop, yousiH*tatemaisu * dtop, null);
printRectangle.setStroke(Color.BLACK);
}
return printRectangle;
}
/**印刷対象のStackPaneを製作する(試験用)
* */
private Node getNodeToPrint() {
//Paneの初期化
StackPane pane = new StackPane();
//必ず左上を原点に設定する
pane.setAlignment( Pos.TOP_LEFT);
Group group = new Group();
//紙の横方向の数
int kkX = yokomaisu ;
//紙の縦方向のサイズ
int kkY = tatemaisu ;
//紙枠のサイズ(仕切りの線)
double kamiX = (yousiW - yohaku * 2.);
double kamiY = (yousiH - yohaku * 2.);
//2点鎖線の設定
Double[] dash = { 20.0, 3.0,3.0,3.0};
//縦の枠線を引く
for(int i=0;i<kkX+1;i++){
Point[] p = new Point[2];
p[0] = ph( new Point( 0.+i*kamiX , 0.));
p[1] = ph( new Point( 0.+i*kamiX , 0.+kkY*kamiY ));
DrawOnPane dop = new DrawOnPane( p , pane ,false);
dop.polyline.setStroke(Color.BLUE);
dop.polyline.getStrokeDashArray().addAll( dash );
}
//横の枠線を引く
for(int i=0;i<kkY+1;i++){
Point[] p = new Point[2];
p[0] = ph( new Point( 0. , 0.+i*kamiY));
p[1] = ph( new Point( 0.+kkX*kamiX , 0.+i*kamiY ));
DrawOnPane dop = new DrawOnPane( p , pane ,false);
dop.polyline.setStroke(Color.BLUE);
dop.polyline.getStrokeDashArray().addAll( dash );
}
//長さ200の線を引く
Point[] p = new Point[2];
p[0] = ph( new Point( 20.,20.));
p[1] = ph( new Point( 20.,220.));
DrawOnPane dop = new DrawOnPane( p , pane ,false);
//円弧の描画
Polyline polyline = new Polyline();
polyline.getPoints().addAll(new Double[]{70.,0.,69.7336288664222,6.10090199233607,68.9365427108546,12.1553724366851,67.6148078402348,18.1173331571765,65.7784834550136,23.9414100327968,63.4415450925655,29.583278321849,60.6217782649107,35.,57.3406431002294,40.1503505445732,53.6231110183285,44.9951326780578,49.4974746830583,49.4974746830583,44.9951326780578,53.6231110183285,40.1503505445732,57.3406431002294,35.,60.6217782649107,29.583278321849,63.4415450925655,23.9414100327968,65.7784834550136,18.1173331571765,67.6148078402348,12.1553724366851,68.9365427108546,6.10090199233609,69.7336288664222,4.28801959218017E-15,70.,-6.10090199233608,69.7336288664222,-12.1553724366851,68.9365427108546,-18.1173331571765,67.6148078402348,-23.9414100327968,65.7784834550136,-29.583278321849,63.4415450925655,-35.,60.6217782649107,-40.1503505445732,57.3406431002294,-44.9951326780578,53.6231110183285,-49.4974746830583,49.4974746830583,-53.6231110183285,44.9951326780578,-57.3406431002294,40.1503505445732,-60.6217782649107,35.,-63.4415450925655,29.583278321849,-65.7784834550136,23.9414100327968,-67.6148078402348,18.1173331571765,-68.9365427108546,12.1553724366851,-69.7336288664222,6.10090199233607,-70.,8.57603918436034E-15,-69.7336288664222,-6.10090199233606,-68.9365427108546,-12.1553724366851,-67.6148078402348,-18.1173331571764,-65.7784834550136,-23.9414100327968,-63.4415450925655,-29.583278321849,-60.6217782649107,-35.,-57.3406431002294,-40.1503505445732,-53.6231110183285,-44.9951326780578,-49.4974746830583,-49.4974746830583,-44.9951326780578,-53.6231110183285,-40.1503505445733,-57.3406431002294,-35.,-60.6217782649107,-29.5832783218489,-63.4415450925655,-23.9414100327969,-65.7784834550136,-18.1173331571764,-67.6148078402348,-12.1553724366851,-68.9365427108546,-6.10090199233608,-69.7336288664222,-1.28640587765405E-14,-70.,6.10090199233605,-69.7336288664222,12.1553724366851,-68.9365427108546,18.1173331571764,-67.6148078402348,23.9414100327968,-65.7784834550136,29.5832783218489,-63.4415450925655,35.,-60.6217782649107,40.1503505445732,-57.3406431002294,44.9951326780578,-53.6231110183285,49.4974746830583,-49.4974746830583,53.6231110183284,-44.9951326780578,57.3406431002294,-40.1503505445733,60.6217782649107,-35.,63.4415450925655,-29.5832783218489,65.7784834550136,-23.9414100327969,67.6148078402348,-18.1173331571764,68.9365427108546,-12.1553724366851,69.7336288664222,-6.10090199233608,70.,0.});
pane.getChildren().add( polyline );
polyline.setTranslateX(100);
return pane;
}
/**用紙の情報を設定する*/
private void setSize(String yousi ,int yokomaisu ,int tatemaisu,double yohaku){
this.tatemaisu = tatemaisu;
this.yokomaisu = yokomaisu;
this.size =yousi;
this.yohaku = yohaku;
if(yousi.equals("A4横")){
yousiW = 297; yousiH = 210;
}else if(yousi.equals("A4縦")){
yousiW = 210; yousiH = 297.;
}else if(yousi.equals("A3横")){
yousiW = 420; yousiH = 297;
}else if(yousi.equals("A3縦")){
}else{
//無ければA4横として扱う
yousiW = 297; yousiH = 210;
}
return;
}
/**点を印刷スケールに変換する*/
private Point ph( Point p ){
Point rp = new Point( p.x * dtop , p.y * dtop );
return rp;
}
}
PrintStackPane
package tomojavalib.fx;
import javafx.print.PageLayout;
import javafx.print.PageOrientation;
import javafx.print.Paper;
import javafx.print.Printer;
import javafx.print.PrinterJob;
import javafx.scene.Node;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Window;
import java.util.ArrayList;
import java.util.List;
/**
* 受け取ったStackPaneを印刷します
*/
public class PrintStackPane {
//1で固定
private double scale = 1.0f;
//印刷不可領域を減じた余白(不可領域を含めてyohakuとなる)
private double yohakuH = 0.;
private double yohakuW = 0.;
//用紙の余白(枠線の用紙端からの距離)
private double yohaku = 10.;
PageLayout pageLayout = null;
Printer printer ;
//用紙の種類A4縦など
private String yousi="";
//印刷範囲を示す四角形
private Rectangle printRectangle;
//印刷する縦横の枚数
int rowCount =1;
int columnCount=1;
/**
* 印刷実行メソッド
*/
public boolean print(PrinterJob job, boolean showPrintDialog, Node node) {
Window window = node.getScene() != null ? node.getScene().getWindow() : null;
if (!showPrintDialog || job.showPrintDialog(window)) {
//printerの取得
printer = Printer.getDefaultPrinter();
//ページサイズと用紙の向き、余白
this.pageLayout = this.getPageLayout( printer , yousi );
this.yohakuH = yohaku - ( pageLayout.getTopMargin() + pageLayout.getBottomMargin())/2. ;
this.yohakuW = yohaku - ( pageLayout.getRightMargin() + pageLayout.getLeftMargin())/2. ;
double pageWidth = pageLayout.getPrintableWidth();
double pageHeight = pageLayout.getPrintableHeight();
//印刷のスケールと縦横枚数を設定
setPrintInfo(pageLayout);
//印刷範囲
double printRectX = this.printRectangle.getX();
double printRectY = this.printRectangle.getY();
double printRectWith = this.printRectangle.getWidth();
double printRectHeight = this.printRectangle.getHeight();
//ノードのクリップ
Node oldClip = node.getClip();
//TransformオブジェクトのObservableListを定義
List<Transform> oldTransforms = new ArrayList<>(node.getTransforms());
//ノードにクリップ領域を設定
node.setClip(new javafx.scene.shape.Rectangle(printRectX, printRectY,printRectWith, printRectHeight));
//スケールの設定(スケールは常に1にしている)
double localScale = 1.;
//0,0,へ移動
node.getTransforms().add(new Translate(-printRectX, -printRectY));
//グリッドへ移動
Translate gridTransform = new Translate();
node.getTransforms().add(gridTransform);
//各ページを印刷する
boolean success = true;
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < columnCount; col++) {
gridTransform.setX(-col * ( pageWidth / localScale -yohakuW*2 ) + yohakuW );
gridTransform.setY(-row * ( pageHeight / localScale -yohakuH*2 ) + yohakuH );
success &= job.printPage(pageLayout, node);
}
}
// ノードとクリップの位置を元に戻す
node.getTransforms().clear();
node.getTransforms().addAll(oldTransforms);
node.setClip(oldClip);
return success;
}
return false;
}
/**
* 印刷する縦横の枚数を計算する
*/
private void setPrintInfo(final PageLayout pageLayout) {
double contentWidth = pageLayout.getPrintableWidth();
double contentHeight = pageLayout.getPrintableHeight();
//スケールは1に固定
double localScale = 1.;
final Rectangle printRect = printRectangle;
final double width = printRect.getWidth() * localScale;
final double height = printRect.getHeight() * localScale;
//全体を印刷するために必要な縦横枚数を計算する
this.columnCount = (int) Math.ceil((width) / contentWidth);
this.rowCount = (int) Math.ceil((height) / contentHeight);
}
/**ページ設定
* yousi:"A4横"など yohaku:紙端から枠線までの距離 Rctangle:印刷範囲
* */
public void setPage( String yousi , double yohaku , Rectangle printRectangle ){
this.yousi = yousi;
this.yohaku = yohaku;
this.printRectangle = printRectangle;
}
private PageLayout getPageLayout( Printer printer , String yousi ){
if(yousi.equals("A4横")){
pageLayout = printer.createPageLayout( Paper.A4 , PageOrientation.LANDSCAPE/*横*/ , Printer.MarginType.HARDWARE_MINIMUM/*余白を最小*/ );
}else if(yousi.equals("A4縦")){
pageLayout = printer.createPageLayout( Paper.A4 , PageOrientation.PORTRAIT/*縦*/ , Printer.MarginType.HARDWARE_MINIMUM/*余白を最小*/ );
}else if(yousi.equals("A3横")){
pageLayout = printer.createPageLayout( Paper.A3 , PageOrientation.LANDSCAPE/*横*/ , Printer.MarginType.HARDWARE_MINIMUM/*余白を最小*/ );
}else if(yousi.equals("A3縦")){
pageLayout = printer.createPageLayout( Paper.A3 , PageOrientation.PORTRAIT/*縦*/ , Printer.MarginType.HARDWARE_MINIMUM/*余白を最小*/ );
}else {
//無ければA4横にしてしまう
pageLayout = printer.createPageLayout( Paper.A4 , PageOrientation.LANDSCAPE/*横*/ , Printer.MarginType.HARDWARE_MINIMUM/*余白を最小*/ );
}
return pageLayout;
}
}//class end
DrawOnPane
package tomojavalib.p2cad;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Polyline;
import tomojavalib.p2cad.Point;
/**
* Pane上に線を表示させる
* */
public class DrawOnPane {
public Polyline polyline =null;
/**
* paneにpolyline,pを描く。Iti trueなら中央falseなら左上を原点にする
* */
public DrawOnPane( Point[] p , StackPane pane , boolean iti){
if(p==null){return;}
if( p.length>1 ){
double[] d = getDouble( p );
polyline = new Polyline( d );
//polyline.getPoints().addAll();
pane.getChildren().add( polyline );
if(iti){
polyline.setTranslateX( polyline.getLayoutBounds().getMinX() + polyline.getLayoutBounds().getWidth() /2. );
polyline.setTranslateY( polyline.getLayoutBounds().getMinY() + polyline.getLayoutBounds().getHeight() /2. );
}else{
polyline.setTranslateX( polyline.getLayoutBounds().getMinX() );
polyline.setTranslateY( polyline.getLayoutBounds().getMinY() );
}
}
}
private double[] getDouble( Point[] p ){
double[] d = new double[ p.length*2 ];
int ii=0;
for(int i=0;i<p.length;i++){
d[ii] = p[i].x;ii++;
d[ii] = p[i].y;ii++;
}
return d;
}
}
最終更新日: 2017-09-22 10:04:18