タツノオトシゴのブログ

主にJavaに関するものです。

JavaFX8の印刷機能によるHTMLのPDF変換

JavaFX8から、印刷機能が完全サポートされる予定。
使い方は、スナップショットによるノード自身を画像として保存する方式とほぼ同じで、
ノード自身をプリンタオブジェクトに渡せばOK。


この印刷機能とPDFに変換する仮想プリンタを利用することで、既存のWEBサイトをPDFに変換できると思い、今回JDK8 Early Access版で試してみた。


JDK8のバージョンは、「JKD8 Developer Preview Build b106(Linux 64bit用)」を使用。
仮想プリンタは、Linuxのcups-pdfを使用した。
OSは、CentOS6.4を使用。


結果印刷時に失敗して、どうにもならなかった
スタックトレースを見ると、印刷時のレイアウトを取得する際に、NullPointerExceptionが発生した。
印刷のレイアウト取得をデフォルトにしたり、ほかの値に変えても同様。
また、Linuxではなく、WindowsXPSやフリーの仮想プリンタで試しても同じ部分でエラーが発生。
さらに、WebViewコントローラではなく、単純なCirculオブジェクトで試しても同様の箇所でエラーが発生。

Firefoxから印刷できるので、仮想プリンタ自体の設定は間違っていなおと思うが原因は不明。
仮想プリンタだからまずいのか単にまだ実装途中なのか不明。
実機のプリンタで試せたらよかったけど、自分は持っていないので、そこまで確認できなかった。


【続報:2014年12月】
2014年12月時点の正式版で試してところ、正常にできました。
詳細は、続・JavaFX8の印刷機能によるHTMLのPDF変換 - タツノオトシゴの日記を参照してください。

Javaのコード

import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.SwingFXUtils;
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.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javax.imageio.ImageIO;

/**
 * @see http://carlfx.wordpress.com/2013/07/15/introduction-by-example-javafx-8-printing/
 */
public class Html2PdfApplication extends Application {
     /**
     * @param args
     */
    public static void main(String[] args) {
        
        String url= "http://stackoverflow.com/questions/16738106/print-contents-of-javafx-tableview";
        
        try {
        launch(new String[]{
                "--url=" + url});
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void start(final Stage primaryStage) throws Exception {
        
        final Application.Parameters params = getParameters();
        final String url = params.getNamed().get("url");
        
        final WebView webView = new WebView();
        WebEngine engine = webView.getEngine();
        engine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {

            @Override
            public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) {
                if(newState == Worker.State.SUCCEEDED) {
                    System.out.println("complete load.");
                    try {
                    saveNodeAsPdf(webView);
                    
                    } finally {
                        // アプリケーションの完了
                        // TODO:印刷ジョブと動機をとる必要がある。
//                        Platform.exit();
                    }
                }
                
            }
        });
        
        engine.load(url);
        primaryStage.setScene(new Scene(webView));
        primaryStage.show();
    }
    
    // 任意のノードを画像に保存する
    private void saveNodeAsImage(final Node node, final File output) {
        final WritableImage image = node.snapshot(new SnapshotParameters(), null);
        
        try {
            ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", output);
        } catch(IOException e) {
            
        }
    }
    
    // 任意のノードを印刷する
    private void saveNodeAsPdf(final Node node) {
    
        // プリンタの取得
        final Printer printer = Printer.getDefaultPrinter();
        if(printer == null) {
            System.err.println("printe is null");
            return;
        } else {
            System.out.println("print name:" + printer.getName());
        }
        
        System.out.println(printer.getPrinterAttributes().getDefaultPaper());
        System.out.println(printer.getPrinterAttributes().getDefaultPageOrientation());
        System.out.println(printer.getPrinterAttributes().getDefaultPrintResolution());
        
        // レイアウトの設定でエラーが発生
        PageLayout pageLayout = printer.getDefaultPageLayout();
//        PageLayout pageLayout = printer.createPageLayout(Paper.A4, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT);
//        PageLayout pageLayout = printer.createPageLayout(Paper.A4, PageOrientation.PORTRAIT, 2,2,2,2 );
//        PageLayout pageLayout = printer.createPageLayout(Paper.NA_LETTER, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT);
    
//        double scaleX = pageLayout.getPrintableWidth() / node.getBoundsInParent().getWidth();
//        double scaleY = pageLayout.getPrintableHeight() / node.getBoundsInParent().getHeight();
//        node.getTransforms().add(new Scale(scaleX, scaleY));
// 
//        PrinterJob job = PrinterJob.createPrinterJob();
        PrinterJob job = PrinterJob.createPrinterJob(printer);
        if (job != null) {
//            if(job.showPrintDialog(null)) {
//                boolean success = job.printPage(node);
                boolean success = job.printPage(pageLayout, node);
                if (success) {
                    job.endJob();
                }
//            }
        }
    }

}

エラー内容

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
	at com.sun.prism.j2d.print.J2DPrinter.printableArea(J2DPrinter.java:844)
	at javafx.print.Printer.createPageLayout(Printer.java:248)
	at javafx.print.Printer.getDefaultPageLayout(Printer.java:220)
	at javafx.print.JobSettings.pageLayoutProperty(JobSettings.java:1097)
	at javafx.print.JobSettings.getPageLayout(JobSettings.java:1133)
	at javafx.print.PrinterJob.printPage(PrinterJob.java:387)
	at sample.Html2PdfApplication.saveNodeAsPdf(Html2PdfApplication.java:165)
	at sample.Html2PdfApplication.access$000(Html2PdfApplication.java:44)
	at sample.Html2PdfApplication$1.changed(Html2PdfApplication.java:80)
	at sample.Html2PdfApplication$1.changed(Html2PdfApplication.java:72)
	at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:176)
	at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
	at javafx.beans.property.ReadOnlyObjectWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyObjectWrapper.java:176)
	at javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:142)
	at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
	at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:145)
	at javafx.scene.web.WebEngine$LoadWorker.updateState(WebEngine.java:995)
	at javafx.scene.web.WebEngine$LoadWorker.dispatchLoadEvent(WebEngine.java:1106)
	at javafx.scene.web.WebEngine$LoadWorker.access$1100(WebEngine.java:988)
	at javafx.scene.web.WebEngine$PageLoadListener.dispatchLoadEvent(WebEngine.java:975)
	at com.sun.webkit.WebPage.fireLoadEvent(WebPage.java:2355)
	at com.sun.webkit.WebPage.fwkFireLoadEvent(WebPage.java:2199)
	at com.sun.webkit.network.URLLoader.twkDidFinishLoading(Native Method)
	at com.sun.webkit.network.URLLoader.notifyDidFinishLoading(URLLoader.java:815)
	at com.sun.webkit.network.URLLoader.access$1200(URLLoader.java:43)
	at com.sun.webkit.network.URLLoader$6.run(URLLoader.java:805)
	at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
	at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
	at com.sun.glass.ui.gtk.GtkApplication.access$200(GtkApplication.java:47)
	at com.sun.glass.ui.gtk.GtkApplication$5$1.run(GtkApplication.java:137)
	at java.lang.Thread.run(Thread.java:724)