原创Java小App解决Jupyter Notebook导出PDF不显示中文

Posted 你啊347

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原创Java小App解决Jupyter Notebook导出PDF不显示中文相关的知识,希望对你有一定的参考价值。

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 }
Main.java

 

  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 }
Main.java

      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();
    }
}
Controller.java

         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());
            }
        }
    }

    //判断是否成功生成,成功生成对应文件弹出成功框

}
Service.java

         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();
    }
}
InputFileDialogController.java

         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" /><

以上是关于原创Java小App解决Jupyter Notebook导出PDF不显示中文的主要内容,如果未能解决你的问题,请参考以下文章

解决Jupyter notebook报错:AssertionError: wrong color format ‘var(--jp-mirror-editor-variable-color)‘(代码片

使用Jupyter notebook的简单说明

python_配置

从 jupyter-notebook 下载 HTML 文件到本地

Pyechart在Jupyter Lab下无法正确显示图形的问题

Jupyter Notebook又一利器nbterm,在终端玩Python!