0.ATTENTION!!!
JavaFx里是通過Java調用控制台執行的的jupyter和xelatex指令,
這些個指令需要在本地安裝Jupyter和MikTeX之后才能正常在電腦上運行
1.【問題背景】
1.1 最近寫了一個大數據的小練習,感覺那個有點用,就想導出PDF去打印
然后問題來了:導出的PDF不顯示中文!!!(可惜那一塊多錢)。
網上教程差不多就是用juypter和xlatex命令進行轉換,但是這樣一個一個轉感覺有點麻煩,
然后就想着寫一個Java程序看看能不能自己選擇文件進行PDF轉換
然后探索的過程就開始了
1.2 一有問題當然是先問度娘了,找了一波后發現可以通過命令提示符(CMD)以命令的方式創建創建出pdf,這就很靈性了。
雖然直接在Jupyter Notebook里可以更簡單地直接導出PDF,
但是對於希望在PDF里顯示中文的同學來說,能夠方便一點導出pdf文件的話,何樂而不為嘞
2.【基本過程】
2.1 我找的解決方案挺簡單的,大概分三步:a): 一條命令通過source.ipynb文件生成source.tex文件
b): 用編輯器打開source.tex,在指定位置添加文本
c):一條命令通過source.tex文件生成一系列文件,這里面就包括了source.pdf
d):上面的處理方式都是通過Java實現,在控制台運行指令也是通過Java調用的(真好玩)
嘿嘿,聽起來挺簡單的,寫着寫着你會發現還真的挺簡單的,還有一點瓜
3.【操作環境以及相關准備】
3.1 win10+IDEA+jdk8+anaconda+Jupyter Notebook+MikTeX+Pandoc+JavaFx(SceneBuilder)
3.2 要通過Jupyter Notebook轉PDF的話要用MikTeX和Pandoc,MikTeX需要配置環境
3.3 MikTeX下載地址:https://miktex.org/download
Pandoc下載地址:https://github.com/jgm/pandoc/releases/tag/2.3.1
3.4 把MikTex添加到系統環境變量里
4.【Java控制台實現方式】
4.1 介紹:最初就是寫了一個控制台程序,然后為了能夠成功導出PDF就一直在堆代碼,最后就是成功導出PDF
4.2 代碼:因為一開始就是為了寫功能而寫代碼,所以感覺把這個寫死了,因為測試就是針對一個文件來寫的,不過問題不大,
后面我又寫了一個JavaFx的,相關注釋我都寫在里面了

1 import java.io.*; 2
3 public class Main { 4
5 private static String path = "D:\\JupyterNotebook"; 6 private static File sourceFile = new File("D:\\JupyterNotebook\\pandas_test.tex"); 7
8 public static void main(String[] args) throws IOException, InterruptedException { 9 for (int i = 0; i < args.length; i++) { 10 System.out.println(args[i]); 11 } 12 change2Tex(); 13 File bufferFile = createTexFile(); //獲取創建的文件對象
14 delay() ; //延時
15 modifyTex(bufferFile); //修該Tex文件
16 change2PDF(); //將Tex文件轉化成PDF文件
17 } 18
19
20 /**
21 * 延一個時, 22 * java建文件比命令提示符快 23 * 24 * @throws InterruptedException 25 */
26 public static void delay() throws InterruptedException { 27 for (int i = 0; i < 4; i++) { 28 Thread.sleep(1000); 29 } 30 } 31
32 /**
33 * 通過簡單的命令 34 * 將文件轉化為tex文件 35 * 執行cmd 36 * 37 * jupyter nbconvert --to latex yourNotebookName.ipynb 38 * 將文件里 \documentclass[11pt]{article}后面加上下面這三行 39 * \\usepackage{fontspec, xunicode, xltxtra} 40 * \\setmainfont{Microsoft YaHei} 41 * 將latex轉化為pdf 42 * xelatex yourNotebookName.tex 43 */
44 public static void change2Tex() throws IOException { 45
46 Runtime runtime = Runtime.getRuntime(); 47 String cmd = "cmd /k start jupyter nbconvert --to latex " + path + "\\pandas_test.ipynb"; //cmd指令,cmd /k start + 指令,運行五玩了指令就關閉cmd
48 System.out.println(cmd); 49 System.out.println(path); 50 runtime.exec(cmd); 51 } 52
53 /**
54 * 將tex文件轉化成pdf文件 55 * 56 * @throws IOException 57 */
58 public static void change2PDF() throws IOException { 59 Runtime runtime = Runtime.getRuntime(); 60 String cmd = "cmd /k start xelatex afterInsertText.tex"; 61 runtime.exec(cmd, null, new File(path));//注意這里:exec里有三個參數,這個方法可以指定在path文件夾打開cmd,然后運行cmd指令
62 } 63
64 /**
65 * 創建Tex文件 66 * 67 * @return
68 * @throws IOException 69 */
70 public static File createTexFile() throws IOException { 71
72 File tempFile = new File("D:\\JupyterNotebook"); //判斷這個文件夾在不在
73 if (!tempFile.exists()) { 74 tempFile.mkdir(); 75 } 76
77 File bufferTopTex = new File("D:\\JupyterNotebook", "afterInsertText.tex"); //判斷這個文件在不在
78 if (!bufferTopTex.exists()) { //不在的話創建文件,
79 bufferTopTex.createNewFile(); 80 } 81 //在后面會把源.tex文件要修改的位置前面的數據寫到下面這個文件(雖然下面是個對象,意思應該能懂)里面, 82 // 然后接着在后面添加文本,最后把源tex剩下的數據寫到這個文件里
83 return bufferTopTex; 84 } 85
86 /**
87 * 文件讀寫,在文件后面添加需要添加的指令 88 * 89 * @throws IOException 90 */
91 public static void modifyTex(File file) throws IOException { 92
93 RandomAccessFile topTex = new RandomAccessFile(file, "rw"); //在本地創建的afterInsertText.tex文件
94
95 RandomAccessFile raf = new RandomAccessFile(sourceFile, "rw"); //獲取源.tex文件
96
97 String line; 98
99 //在下面是對readLine()取到的數據進行轉碼,這是一個解決亂碼的好方式 100 //注意在下面每用一次readLine(),那個指向行號的指針就會向下移動一次,和ResultSet里的rs.next()有點像
101 while ((line = new String(raf.readLine().getBytes("ISO-8859-1"), "utf-8")) != null) { 102 topTex.write(("\n" + line).getBytes()); 103 if (line.equals("\\documentclass[11pt]{article}")) { 104 topTex.write((" \n\\usepackage{fontspec, xunicode, xltxtra}").getBytes()); 105 topTex.write(("\n\\setmainfont{Microsoft YaHei}").getBytes()); 106 topTex.write(("\n\\usepackage{ctex} ").getBytes()); 107 break; 108 } 109 } 110
111 while (true) { 112 final String temp; 113 if ((temp = raf.readLine()) == null) { 114 break; 115 } else { 116 line = new String(temp.getBytes("ISO-8859-1"), "utf-8"); 117 topTex.write(("\n" + line).getBytes()); 118 } 119 } 120 } 121 }
4.3 運行結果:emmmmmmm,這個運行完了就自己關掉了,不好截圖,看看其他的吧
4.3.1 只有一個.ipynb文件:
然后運行一下程序:
先是Java程序生成的afterInsertText.tex文件
然后接着運行到結束就會生成這些文件,可以看到pdf自動生成了
最后看一眼有沒有中文:
-----------------可以看出,上面的pdf是有中文的,成功-----------------
Tip_1:在這里給出要用到的cmd命令:
1.jupyter nbconvert --to latex yourNotebookName.ipynb
2.將文件里 \documentclass[11pt]{article}后面加上下面這三行
\usepackage{fontspec, xunicode, xltxtra}
\setmainfont{Microsoft YaHei}
\usepackage{ctex}
3.將latex轉化為pdf: xelatex yourNotebookName.tex
Tip_2:
在Java里輸出反斜杠要用兩個,英文點號 ......要用 \\.
5.【JavaFx實現方式】
5.1 用的是IDEA開發的,所以開發JavaFx程序挺簡單的,只要新建一個JavaFx程序就好了,然后就是下載Scenebuilder了,下載完了之后還要配置一下options.xml和other.xml
路徑是這個:other.xml也在這個路徑下
配置:other.xml
配置:options.xml,在45行里的value里面加上E:/SceneBuilder/SceneBuilder.exe;你的SceeBuilder程序的路徑
重啟IDEA,右鍵FXML文件可以看到有open in scenebuilder那個選項點擊之后不會報錯了
5.2 程序總體來說還行,但是打包成jar包之后就會出bug,點擊Generate不會正常顯示提示框,可是在IDEA里可以正常顯示提示框,不知道為啥,所以就沒處理這個bug,有興趣的可以改一改咯
5.3 Java調用控制台執行的的jupyter和xelatex指令,這些個指令需要在本地安裝Jupyter和MikTeX之后才能正常在電腦上運行
5.4 程序基本骨架
5.5 程序代碼:
5.5.1 包含main方法的類

1 package sample; 2 3 import javafx.application.Application; 4 import javafx.fxml.FXMLLoader; 5 import javafx.scene.Parent; 6 import javafx.scene.Scene; 7 import javafx.stage.Stage; 8 9 public class Main extends Application { 10 11 static Stage mainStage = null ; 12 13 public static Stage getMainStage(){ 14 return mainStage; 15 } 16 @Override 17 public void start(Stage primaryStage) throws Exception{ 18 mainStage = primaryStage; 19 Parent root = FXMLLoader.load(getClass().getResource("view/sample.fxml")); 20 primaryStage.setTitle("ChangeTex2PDF"); 21 primaryStage.setScene(new Scene(root)); 22 primaryStage.show(); 23 } 24 25 26 public static void main(String[] args) { 27 launch(args); 28 } 29 }
5.5.2 主頁中的控件的控制器

package sample.controller; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.stage.FileChooser; import javafx.stage.Stage; import sample.Main; import sample.Service; import java.io.File; import java.io.IOException; public class Controller { InputFileDialogController InputFileDialogController = new InputFileDialogController(); private String srcFilePath; @FXML private Label filePath_Label; @FXML private void importFile() { //創建選擇文件stage Stage chooseFileStage = new Stage(); FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("選擇文件"); //文件過濾 fileChooser.getExtensionFilters().addAll( new FileChooser.ExtensionFilter("ipynb file", "*.ipynb") ); //以對話框的形式顯示選擇文件 File file = fileChooser.showOpenDialog(chooseFileStage); if (file != null) { String absolutePath = file.getAbsolutePath(); srcFilePath = absolutePath; filePath_Label.setText(absolutePath); System.out.println("勞資要讀文件啦"); } } @FXML /** * 生成按鈕點擊事件處理 * srcFilePath:文件的絕對路徑 * fullName:名稱+后綴 * filePkgPath:目標文件的包路徑, * 比如:C:\test\notebook\test.ipynb。 * 這里filePkgPath就是C:\test\notebook * path_arr數組:對文件絕對路徑的拆分,按反斜杠 \ 進行拆分,在java里用\\ * fullName_arr數組:對fullName按英文句號進行拆分,在java里用"\\."表示 * texFileName:構造一個tex文件名,給后面轉化pdf時用 * */ public void generatePDF() throws IOException, InterruptedException { if (srcFilePath != null) { //在選擇了文件的情況下 String[] path_arr = srcFilePath.split("\\\\"); String fullName = path_arr[path_arr.length - 1]; StringBuffer filePkgPath = getStringBuffer(path_arr); String[] fullName_arr = fullName.split("\\."); Service.change2Tex(filePkgPath.toString(), fullName_arr[0]); File bufferFile = Service.createTexFile(filePkgPath.toString()); Service.delay(); String texFileName = fullName_arr[0] + ".tex"; texFileName = filePkgPath + "\\" + texFileName; Service.modifyTex(bufferFile, texFileName); Service.change2PDF(filePkgPath.toString()); } else { InputFileDialogController.loadDialog().show(); //顯示提示框,請先選擇所需文件 Main.getMainStage().close(); } } /** * 獲取目標文件的包路徑 * * @param path_arr * @return */ private StringBuffer getStringBuffer(String[] path_arr) { StringBuffer filePkgPath = new StringBuffer(); for (int i = 0; i < path_arr.length - 1; i++) { if (i < path_arr.length - 2) { //除開絕對路徑后面的(文件名+后綴)那一項 filePkgPath.append(path_arr[i]).append("\\"); } else { filePkgPath.append(path_arr[i]); } } return filePkgPath; } /** * 退出軟件 * * @throws IOException */ @FXML public void exit() throws IOException { Main.getMainStage().close(); } }
5.5.3 調用控制台,執行jupyter xlatex指令,以及對文件路徑進行處理的方法

package sample; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class Service { /** * 延一個時, * 娘欸java建文件比命令提示符快 * * @throws InterruptedException */ public static void delay() throws InterruptedException { for (int i = 0; i < 4; i++) { Thread.sleep(1000); } } /** * 通過簡單的命令 * 將文件轉化為tex文件 * * 執行cmd * * jupyter nbconvert --to latex yourNotebookName.ipynb * * 將文件里 \documentclass[11pt]{article}后面加上下面這兩行 * * \\usepackage{fontspec, xunicode, xltxtra} * * \\setmainfont{Microsoft YaHei} * * 將latex轉化為pdf * * xelatex yourNotebookName.tex */ public static void change2Tex(String filePkgPath, String fileName) throws IOException { Runtime runtime = Runtime.getRuntime(); String cmd = "cmd /k start jupyter nbconvert --to latex " + filePkgPath + "\\" + fileName + ".ipynb"; runtime.exec(cmd); } /*************************************這里可以改,改成用戶自定義名稱************* * 將tex文件轉化成pdf文件 * * @throws IOException */ public static void change2PDF(String filePkgPath) throws IOException { Runtime runtime = Runtime.getRuntime(); String cmd = "cmd /k start xelatex Result.tex"; runtime.exec(cmd, null, new File(filePkgPath)); } /** * 創建Tex文件 * * @return 獲取生成的tex文件 * @throws IOException */ public static File createTexFile(String filePkgPath) throws IOException { File tempFile = new File(filePkgPath); if (!tempFile.exists()) { tempFile.mkdir(); } File bufferTopTex = new File(filePkgPath, "Result.tex"); if (!bufferTopTex.exists()) { bufferTopTex.createNewFile(); } return bufferTopTex; } /** * 文件讀寫,在文件后面添加需要添加的指令 * * @throws IOException */ public static void modifyTex(File file, String sourceFile) throws IOException { RandomAccessFile topTex = new RandomAccessFile(file, "rw"); RandomAccessFile raf = new RandomAccessFile(sourceFile, "rw"); String line; while ((line = new String(raf.readLine().getBytes("ISO-8859-1"), "utf-8")) != null) { topTex.write(("\n" + line).getBytes()); if (line.equals("\\documentclass[11pt]{article}")) { topTex.write((" \n\\usepackage{fontspec, xunicode, xltxtra}").getBytes()); topTex.write(("\n\\setmainfont{Microsoft YaHei}").getBytes()); topTex.write(("\n\\usepackage{ctex} ").getBytes()); break; } } //接着插入后半部分 while (true) { final String temp; if ((temp = raf.readLine()) == null) { break; } else { line = new String(temp.getBytes("ISO-8859-1"), "utf-8"); topTex.write(("\n" + line).getBytes()); } } } //判斷是否成功生成,成功生成對應文件彈出成功框 }
5.5.4 控制彈出框動作的控制器

package sample.controller; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.stage.Stage; import sample.Main; import java.io.IOException; public class InputFileDialogController { static Stage stage = new Stage(); public Stage loadDialog() throws IOException { Scene dialogScene = new Scene(FXMLLoader.load(getClass().getResource("../view/inputFileDialog.fxml"))); stage.setScene(dialogScene); return stage; } @FXML public void closeDialog(){ stage.close(); Main.getMainStage().show(); } }
5.5.5 主頁的視圖

1 <?xml version="1.0" encoding="UTF-8"?> 2 3 <?import javafx.scene.control.Button?> 4 <?import javafx.scene.control.Label?> 5 <?import javafx.scene.layout.AnchorPane?> 6 <?import javafx.scene.layout.ColumnConstraints?> 7 <?import javafx.scene.layout.GridPane?> 8 <?import javafx.scene.layout.HBox?> 9 <?import javafx.scene.layout.RowConstraints?> 10 <?import javafx.scene.text.Font?> 11 12 <AnchorPane maxHeight="354.0" maxWidth="400.0" minHeight="295.0" minWidth="400.0" prefHeight="338.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.controller.Controller"> 13 <children> 14 <GridPane prefHeight="343.0" prefWidth="400.0" style="-fx-background-color: white;" stylesheets="@giveMeCss.css"> 15 <columnConstraints> 16 <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> 17 </columnConstraints> 18 <rowConstraints> 19 <RowConstraints maxHeight="164.0" minHeight="10.0" prefHeight="59.0" vgrow="SOMETIMES" /> 20 <RowConstraints maxHeight="241.0" minHeight="10.0" prefHeight="224.0" vgrow="SOMETIMES" /> 21 <RowConstraints maxHeight="52.0" minHeight="10.0" prefHeight="52.0" vgrow="SOMETIMES" /> 22 </rowConstraints> 23 <children> 24 <AnchorPane maxHeight="50.0" minHeight="50.0" minWidth="0.0" prefHeight="50.0" prefWidth="398.0"> 25 <children> 26 <Label layoutX="62.0" prefHeight="51.0" prefWidth="277.0" stylesheets="@giveMeCss.css" text="Change Tex to PDF"> 27 <font> 28 <Font name="Consolas Bold" size="28.0" /> 29 </font> 30 </Label> 31 </children> 32 </AnchorPane> 33 <Button mnemonicParsing="false" onMouseClicked="#generatePDF" prefHeight="61.0" prefWidth="400.0" style="-fx-background-color: #007bff;" stylesheets="@giveMeCss.css" text="Generate" textFill="WHITE" GridPane.rowIndex="2"> 34 <font> 35 <Font size="23.0" /> 36 </font> 37 </Button> 38 <Label fx:id="filePath_Label" alignment="CENTER" contentDisplay="CENTER" prefHeight="34.0" prefWidth="381.0" text=" ......................0.0_Powerby_ShiJie....................." textFill="#f56744" GridPane.rowIndex="1"> 39 <font> 40 <Font size="17.0" /> 41 </font> 42 </Label> 43 <HBox prefHeight="178.0" prefWidth="400.0" GridPane.rowIndex="1"> 44 <children> 45 <Button cache="true" mnemonicParsing="false" onMouseClicked="#importFile" prefHeight="34.0" prefWidth="137.0" style="-fx-background-color: lightgreen;" stylesheets="@giveMeCss.css" text="Select File..."> 46 <font> 47 <Font size="16.0" /> 48 </font> 49 </Button> 50 <Button mnemonicParsing="false" opacity="0.0" prefHeight="35.0" prefWidth="141.0" text="Button" /> 51 <Button mnemonicParsing="false" onMouseClicked="#exit" prefHeight="34.0" prefWidth="129.0" style="-fx-background-color: #ff5757;" stylesheets="@giveMeCss.css" text="Exit" textFill="#ffffffbd"> 52 <font> 53 <Font name="System Bold" size="16.0" /> 54 </font> 55 </Button> 56 </children> 57 </HBox> 58 </children> 59 </GridPane> 60 </children> 61 </AnchorPane>
5.5.6 彈出框的視圖

1 <?xml version="1.0" encoding="UTF-8"?> 2 3 <?import javafx.scene.control.Button?> 4 <?import javafx.scene.control.Label?> 5 <?import javafx.scene.layout.AnchorPane?> 6 <?import javafx.scene.text.Font?> 7 8 <AnchorPane prefHeight="238.0" prefWidth="411.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.controller.InputFileDialogController"> 9 <children> 10 <Label alignment="CENTER" layoutY="-6.0" prefHeight="53.0" prefWidth="411.0" style="-fx-background-color: #ff4d4d;" stylesheets="@giveMeCss.css" text="Warning" textFill="#e9ff4f"> 11 <font> 12 <Font name="System Bold" size="21.0" /> 13 </font></Label> 14 <Label layoutX="84.0" layoutY="72.0" prefHeight="95.0" prefWidth="267.0" text="Please select a file first ;)"> 15 <font> 16 <Font size="20.0" /> 17 </font></Label> 18 <Button layoutX="161.0" layoutY="195.0" mnemonicParsing="false" onMouseClicked="#closeDialog" prefHeight="43.0" prefWidth="90.0" style="-fx-background-color: #007bff;" stylesheets="@giveMeCss.css" text="OK" textFill="#c4f2ff"> 19 <font> 20 <Font size="16.0" /> 21 </font></Button> 22 </children> 23 </AnchorPane>
5.6 Tips:
5.6.1 如果你直接導入這個項目的話,那么你在SceneBuilder里需要選定controller,以及綁定控件觸發事件和控件id
5.7 最后再上一波運行結果哇
5.7.1首頁
5.7.2 沒有選擇文件點擊Generate之后:在開發環境里點擊會彈出Warning,但是導出jar包之后就報錯了0.0
5.7.3 選擇.ipynb文件,然后程序就開始了
5.7.4 還有一些細節可以做,比如說程序運行的進度條呀,自定義導出pdf的名稱呀啥的
可能我的解決方式有點瓜,但是這只是我一時興起,於是就寫了這么個東西,覺得挺好玩的
代碼已經放到我的Github上了,歡迎大家來看看呀:https://github.com/Shijie1210/ChangeTex2PDF_CN.git