ともさんのHP >プロブラミング >JavaFX >JavaFX印刷

JavaFX複数ページの印刷

JavaFXで描画した線をポスター印刷するサンプルプログラムです。
私の作っているフリーソフト、洋裁CADのために書いたコードを抜粋して紹介します。

広告

SwingとFXの印刷解像度

元々 Java 楽天 Swingで記述したものなのですが、Swingでは印刷解像度が72DPI固定で、線がギザギザになってしまいます。
JavaFX楽天 は解像度をしていできないものの、Swing以上の解像度が可能なので、印刷部分だけをFXに差し替えました。
下の写真左側がSwing、右がFXで出力したものです。差は歴然としていますね。
JavaFxSwing印刷比較


線の描画

このページで紹介する印刷は、画像等は含まず、すべて線になります(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を印刷する

DrawOnPane
 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

ともさんのHP >プロブラミング >JavaFX >JavaFX印刷

広告
新着ページ

渋柿で柿渋作り  
新旧文化式とドレメ式原型製図を比較  
水棚の作り方  
DXFファイルの違い  
デジタル顕微カメラ  
萩の栽培、手入れ、増やしかた  
セミの飼いかた  
ダンゴムシの飼い方  
玉縁ボタンホールの縫い方  
柿渋  
ミシンでボタンを付ける  

他のサイト

3D-CAD
洋裁CAD

いいねなど

 RSS 

Author: Tomoyuki Ito

このサイトの文章・写真の無断転載を禁じます