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ではなく、WindowsでXPSやフリーの仮想プリンタで試しても同じ部分でエラーが発生。
さらに、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)