wkhtmltopdf+itext实现html生成pdf文件的打印下载(适用于linux及windows)

简介: 目中遇到个根据html转Java的功能,在java中我们itext可以快速的实现pdf打印下载的功能,在itext中我们一般有以下三中方式实现配置pdf模板,通过Adobe Acrobat 来设置域最后通过代码将数据填充进去通过FreeMarker或thymeleaf配置html模板填充数据Jsoup+XMLWorkerHelper对于上述的三种方式,我简述下我的体验:第一种方式对于入门简单,如果我们需求中的pdf文件是表格或者报表的样式还是很好实现的,但如果遇到要求和html样式一致的话就基本歇菜了。

目中遇到个根据html转Java的功能,在java中我们itext可以快速的实现pdf打印下载的功能,在itext中我们一般有以下三中方式实现

  1. 配置pdf模板,通过Adobe Acrobat 来设置域最后通过代码将数据填充进去
  2. 通过FreeMarker或thymeleaf配置html模板填充数据
  3. 对于上述的三种方式,我简述下我的体验:第一种方式对于入门简单,如果我们需求中的pdf文件是表格或者报表的样式还是很好实现的,但如果遇到要求和html样式一致的话就基本歇菜了。第二张方式比较理想,在项目基本完工的情况下再去改成模板不太现实,而且我只是个做后端的还没那么大的能耐,不选。第三种方式我也尝试了下,对于一些简单网页比如说博客,我们通过Jsoup可以获取到文章的内容和html样式,网上的demo也是通过博客来举例的,效果不错,但我看了下博客内容的基本样式就是一些基本的div、p、li这些标签,但在我实际的项目中样式比较复杂,生成的pdf无法打开。而且上面的三种方式都有一个致命的缺点:那就是需要去针对模板。现实的项目中我们可能多个地方需要实现pdf打印的功能,样式模板的配置将占用很大的开发工作量。第三种方式虽说不需要模板,但通过网页的标签去获取数据也会变得多样化,完全不能实现方法的复用。当然,itext也不是一无是处,在这个项目里面我们还是需要用到itext去生成水印、加密、不可编辑等一系列细致的活。

    这里我想要的理想的pdf打印效果是:最少的改动,实现pdf的打印的效果与我请求的url的html样式一致。那就要使用到我们今天的主角wkhtmltopdf,完美的解决了我的难题,windows下解压即用,linux下需要安装三个依赖和一个宋体文字,下面让我们一起coding吧~

    详细步骤:

    1. windows下wkhtmltopdf的下载安装测试     
    2. linux下的wkhtmltopdf的下载安装测试
    3. 配置pdf生成的路径解读
    4. 自动调用java中Runtime.getRuntime()运行wkhtmltopdff插件的具体实现

     

    windows下安装测试

    通过https://wkhtmltopdf.org/downloads.html下载并解压缩wkhtmltox文件,通过cmd 命令找到 bin下面的wkhtmltopdf.exe 文件,执行wkhtmltopdf.exe +http url + pdf路径及文件名

    http://image.tianluzhi.com:81/windows.mp4  windows演示地址

    Linux下安装测试

    wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4.tar.bz2 用wget下载wkhtmltopdf安装,这里我们采用的是0.12.4的版本,没有和上面windows的0.12.5保持一致原因是没有0.12.5的tar包,rpm的不习惯。安装完之后我们参考上面windows的方式运行,运行过程中在centos 7中会遇到缺少依赖的问题。执行yum -y install libXrender* libfontconfig* libXext* 安装后重新运行测试,还会遇到中文乱码问题,这这里我们还需将windows下的simsun字体放置到 /usr/share/fonts/ ,windows字体的存放位置为C:\Windows\Fonts,win10部分用户的simsun字体是没效果的,这里给大家提供一个下载  http://image.tianluzhi.com:81/SIMSUN.TTC ,linux下一般就这些问题。这里就不做演示了,我们开始真正的coding吧。

     

    配置pdf生成的路径解读

    
    # linux path
    #wkhtmltopdfPath 指向wkhtmltopdfPath安装的bin目录下wkhtmltopdf
    wkhtmltopdfPath=/home/wkhtmltox/bin/wkhtmltopdf
    #pdf
    pdfLocationPathForLinux=/home/prd-gehcpp-01/otoResources/pdf/
    waterPdfLocationPathForLinux=/home/prd-gehcpp-01/otoResources/waterPdf/
    
    # windows path
    #photoLocationPath=D://otoResources//photo//
    #pdf文件路径
    pdfLocationPath= D://otoResources//pdf//
    #添加水印后文件路径
    waterPdfLocationPath= D://otoResources//waterPdf//
    

    上面配置中我们分别配置了linux和windows两套路径环境,在我们代码中我们会自动判别当前服务器的操作系统,然后选择相关的路径。下面我们分别解读下各个配置参数

    wkhtmltopdfPath :指向wkhtmltopdfPath安装的bin目录下wkhtmltopdf,这段地址其实就是为了执行wkhtmltopdf插件而添加的,这里我们用全路径,这样在linux的环境下我们就不需要去配置环境了,另外细心的朋友会发现windows的没有配置,这是因为我已经将插件放到了资源文件夹下,
    然后通过Java程序去拼接了一个完整的路径,具体我们往下看。
    pdfLocationPathForLinux/photoLocationPath : 这两个路径分别指向了linux和Windows下的pdf生成的初始文件,如果我们不需要对pdf进行二次操作的话,在这里我们就可以把结果返回给用户。
    waterPdfLocationPathForLinux/waterPdfLocationPath :这个路径是指经过加工后的路径,比如水印、加密、不可编辑等,上面的路径相当于是模子,这个才是最终返回客户的结果。 

    代码实现

    部分代码参考 https://www.cnblogs.com/xionggeclub/p/6144241.html

    controller

    /**
    *
    * @param response
    * @param httpUrl 需要生成pdf的请求地址
    * @param downName 生成pdf后的文件名
    * @return
    */
    @RequestMapping("pdfConvert")
    @ResponseBody
    public Response htmlToPdf(HttpServletResponse response ,@RequestParam String httpUrl,String downName) {
    return geFileService.htmlToPdf(response,httpUrl,downName);
    }

    service 

       Response htmlToPdf(HttpServletResponse response, String httpUrl,String downName);

    impl

     
       
    package com.ge.service.impl;

    import com.common.exception.MyException;
    import com.ge.service.GeFileService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import pdf.HtmlToPdfUtil;
    import utils.Response;
    import utils.ResultHttpStatus;

    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    import java.util.UUID;

    import static utils.PropertiesUtil.getProperty;

    /**
    * @Author: gaofeng_peng
    * @Date: 2018/6/13 13:47
    */
    @Service("GeFileService")
    public class FileServiceImpl implements GeFileService {
    private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);

    public static String pdfLocationPath;

    public static String waterPdfLocationPath;

    public static String systemOs;
    /**
    * 根据服务器系统设置pdf文件路径
    */
    static {
    systemOs = System.getProperty("os.name");
    if (systemOs.toLowerCase().startsWith("win")) {
    pdfLocationPath = getProperty("pdfLocationPath");
    waterPdfLocationPath = getProperty("waterPdfLocationPath");
    } else {
    pdfLocationPath = getProperty("pdfLocationPathForLinux");
    waterPdfLocationPath = getProperty("waterPdfLocationPathForLinux");
    }
    }



    @Override
    public Response htmlToPdf(HttpServletResponse res, String httpUrl, String downName) {
    Response response = new Response(ResultHttpStatus.OK.getValue(), ResultHttpStatus.OK.getName());
    //判断路径是否存在,不存在则创建
    File fileDir = new File(pdfLocationPath);
    File waterFileDir = new File(waterPdfLocationPath);
    if (!fileDir.exists()) {
    fileDir.setWritable(true);
    fileDir.mkdirs();
    }
    if (!waterFileDir.exists()) {
    waterFileDir.setWritable(true);
    waterFileDir.mkdirs();
    }
    String fileName = UUID.randomUUID().toString() + ".pdf";
    String source = pdfLocationPath + fileName;
    String outPath = waterPdfLocationPath + fileName;
    try {
    //调用HtmlToPdfUtilz中的convent的方法使html生成pdf
    boolean isSuccess = HtmlToPdfUtil.convert(httpUrl, source,systemOs);
    if (isSuccess) {
    //水印加密
    // HtmlToPdfUtil.setWaterMarkForPDF(source,outPath,"");
    download(res, downName, fileName);
    } else {
    throw new MyException("pdf下载异常");
    }

    } catch (Exception e) {
    response.setMsg(e.getMessage());
    response.setStatus(ResultHttpStatus.INTERNAL_ERROR.getValue());
    }
    return response;
    }

    /**
    * 响应下载
    * @param resp
    * @param downloadName 下载的pdf文件名
    * @param fileName 系统中真正的文件名
    */
        public void download(HttpServletResponse resp, String downloadName, String fileName) {
        
    try {
    downloadName = new String(downloadName.getBytes("GBK"), "ISO-8859-1");
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    }
    String realPath = pdfLocationPath;
    String path = realPath + fileName;
    File file = new File(path);
    resp.reset();
    resp.setContentType("application/octet-stream");
    resp.setCharacterEncoding("utf-8");
    resp.setContentLength((int) file.length());
    resp.setHeader("Content-Disposition", "attachment;filename=" + downloadName + ".pdf");
    byte[] buff = new byte[1024];
    BufferedInputStream bis = null;
    OutputStream os = null;
    try {
    os = resp.getOutputStream();
    bis = new BufferedInputStream(new FileInputStream(file));
    int i = 0;
    while ((i = bis.read(buff)) != -1) {
    os.write(buff, 0, i);
    os.flush();
    }
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    bis.close();
    //完成后删除本地pdf文件
    file.delete();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    }
    }
     

     

    HtmlToPdfUtil类

    package pdf;
    
    
    import com.itextpdf.text.BaseColor;
    import com.itextpdf.text.pdf.BaseFont;
    import com.itextpdf.text.pdf.PdfContentByte;
    import com.itextpdf.text.pdf.PdfReader;
    import com.itextpdf.text.pdf.PdfStamper;
    import org.apache.commons.lang3.StringUtils;
    import utils.PropertiesUtil;
    
    import java.io.FileOutputStream;
    
    /**
     * @Author: gaofeng_peng
     * @Date: 2018/7/5 13:13
     */
    public class HtmlToPdfUtil {
        //wkhtmltopdf在系统中的路径
        // private static final String toPdfTool = "D:\\wkhtmltox\\bin\\wkhtmltopdf.exe";
    
    
        /**
         * html转pdf
         *
         * @param srcPath    html路径
         * @param pdfLocationPath pdf保存路径
         * @return 转换成功返回true
         */
        public static boolean convert(String srcPath, String pdfLocationPath, String systemOs) throws Exception {
            String toPdfTool="";
            if (systemOs.toLowerCase().startsWith("win")) {
                 toPdfTool = HtmlToPdfUtil.class.getClassLoader().getResource("wkhtmltox/bin/wkhtmltopdf.exe").getPath();
            }else {
                toPdfTool= PropertiesUtil.getProperty("wkhtmltopdfPath");
            }
            StringBuilder cmd = new StringBuilder();
            cmd.append(toPdfTool);
            cmd.append(" ");
            cmd.append("  --background");
            cmd.append(" --debug-javascript");
            cmd.append("  --header-line");//页眉下面的线
            cmd.append(" --header-spacing 10 ");//    (设置页眉和内容的距离,默认0)
    
            cmd.append(srcPath);
            cmd.append(" ");
            cmd.append(pdfLocationPath);
    
            boolean result = true;
            try {
                Process proc = Runtime.getRuntime().exec(cmd.toString());
                HtmlToPdfInterceptor error = new HtmlToPdfInterceptor(proc.getErrorStream());
                HtmlToPdfInterceptor output = new HtmlToPdfInterceptor(proc.getInputStream());
                error.start();
                output.start();
                proc.waitFor();
            } catch (Exception e) {
                result = false;
                e.printStackTrace();
            }
            return result;
        }
    
        /**
    * 生成水印,加密在这里实现 * @param sourceFilePath 源文件路径 * @param fileWaterMarkPath 水印生成文件路径 * @throws Exception */ public static void setWaterMarkForPDF(String sourceFilePath, String fileWaterMarkPath, String waterMarkName) throws Exception { // String waterPath = Class.class.getClass().getResource("/1.png").getPath(); PdfReader reader = new PdfReader(sourceFilePath); PdfStamper stamp = new PdfStamper(reader, new FileOutputStream(fileWaterMarkPath)); int total = reader.getNumberOfPages() + 1; PdfContentByte under = null; //Image img = Image.getInstance(waterPath); //img.setAbsolutePosition(30, 100);//坐标 // img.setRotation(-20);//旋转 弧度 //img.setRotationDegrees(-35);//旋转 角度 //img.scaleAbsolute(200,100);//自定义大小 //img.scalePercent(100);//依照比例缩放 BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED); if (StringUtils.isBlank(waterMarkName)) { waterMarkName = "Tech Mahindra"; } int j = waterMarkName.length(); char c = 0; int rise = 0; for (int i = 1; i < total; i++) // 每一页都加水印 { rise = 500; under = stamp.getUnderContent(i); // 添加图片 // under.addImage(img); under.beginText(); under.setColorFill(BaseColor.BLACK); under.setFontAndSize(bf, 30); // 设置水印文字字体倾斜 开始 if (j >= 15) { under.setTextMatrix(200, 120); for (int k = 0; k < j; k++) { under.setTextRise(rise); c = waterMarkName.charAt(k); under.showText(c + ""); rise -= 20; } } else { under.setTextMatrix(180, 100); for (int k = 0; k < j; k++) { under.setTextRise(rise); c = waterMarkName.charAt(k); under.showText(c + ""); rise -= 18; } } // 字体设置结束 under.endText(); } stamp.close(); reader.close(); } }
    HtmlToPdfInterceptor类
    package pdf;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    /**
     * @Author: gaofeng_peng
     * @Date: 2018/7/5 13:17
     */
    public class HtmlToPdfInterceptor extends Thread {
        private InputStream is;
    
        public HtmlToPdfInterceptor(InputStream is){
            this.is = is;
        }
    
        public void run(){
            try{
                InputStreamReader isr = new InputStreamReader(is, "utf-8");
                BufferedReader br = new BufferedReader(isr);
                String line = null;
                while ((line = br.readLine()) != null) {
                    System.out.println(line.toString()); //输出内容
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    水印部分的功能被我注释了,实际效果是可行的,只是本人设计的太丑,没脸见人所以注释。后期改良再说,有兴趣的小伙伴可以自己改良。至此pdf打印下载的功能就完成了。需要注意的是html最好用原生的,使用less预处理可能会导致样式的丢失。

     

相关文章
|
3月前
|
存储 JSON API
如何将 Swagger 文档导出为 PDF 文件
你会发现自己可能需要将 Swagger 文档导出为 PDF 或文件,以便于共享和存档。在这篇博文中,我们将指导你完成将 Swagger 文档导出为 PDF 格式的过程。
|
2月前
|
人工智能 算法 安全
使用CodeBuddy实现批量转换PPT、Excel、Word为PDF文件工具
通过 CodeBuddy 实现本地批量转换工具,让复杂的文档处理需求转化为 “需求描述→代码生成→一键运行” 的极简流程,真正实现 “技术为效率服务” 的目标。感兴趣的快来体验下把
89 10
|
4月前
|
Unix Linux 编译器
windows下和linux下cmake的规则有区别吗
通过合理使用CMake的条件逻辑和平台特定的配置选项,开发者可以编写更加灵活和健壮的CMake脚本,确保项目在Windows和Linux上的一致性和可移植性。
202 76
|
2月前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
93 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
5月前
|
人工智能 编解码 文字识别
OCRmyPDF:16.5K Star!快速将 PDF 文件转换为可搜索、可复制的文档的命令行工具
OCRmyPDF 是一款开源命令行工具,专为将扫描的 PDF 文件转换为可搜索、可复制的文档。支持多语言、图像优化和多核处理。
672 17
OCRmyPDF:16.5K Star!快速将 PDF 文件转换为可搜索、可复制的文档的命令行工具
|
5月前
|
文字识别 Serverless 开发工具
【全自动改PDF名】批量OCR识别提取PDF自定义指定区域内容保存到 Excel 以及根据PDF文件内容的标题来批量重命名
学校和教育机构常需处理成绩单、报名表等PDF文件。通过OCR技术,可自动提取学生信息并录入Excel,便于统计分析和存档管理。本文介绍使用阿里云服务实现批量OCR识别、内容提取、重命名及导出表格的完整步骤,包括开通相关服务、编写代码、部署函数计算和设置自动化触发器等。提供Python示例代码和详细操作指南,帮助用户高效处理PDF文件。 链接: - 百度网盘:[链接](https://pan.baidu.com/s/1mWsg7mDZq2pZ8xdKzdn5Hg?pwd=8866) - 腾讯网盘:[链接](https://share.weiyun.com/a77jklXK)
464 5
|
5月前
|
文字识别 BI
【图片型PDF】批量识别扫描件PDF指定区域局部位置内容,将识别内容导出Excel表格或批量改名文件,基于阿里云OCR对图片型PDF识别改名案例实现
在医疗和政务等领域,图片型PDF文件(如病历、报告、公文扫描件)的处理需求广泛。通过OCR技术识别这些文件中的文字信息,提取关键内容并保存为表格,极大提高了信息管理和利用效率。本文介绍一款工具——咕嘎批量OCR系统,帮助用户快速处理图片型PDF文件,支持区域识别、内容提取、导出表格及批量改名等功能。下载工具后,按步骤选择处理模式、进行区域采样、批量处理文件,几分钟内即可高效完成数百个文件的处理。
532 8
|
22天前
|
JSON 自然语言处理 Linux
linux命令—tree
tree是一款强大的Linux命令行工具,用于以树状结构递归展示目录和文件,直观呈现层级关系。支持多种功能,如过滤、排序、权限显示及格式化输出等。安装方法因系统而异常用场景包括:基础用法(显示当前或指定目录结构)、核心参数应用(如层级控制-L、隐藏文件显示-a、完整路径输出-f)以及进阶操作(如磁盘空间分析--du、结合grep过滤内容、生成JSON格式列表-J等)。此外,还可生成网站目录结构图并导出为HTML文件。注意事项:使用Tab键补全路径避免错误;超大目录建议限制遍历层数;脚本中推荐禁用统计信息以优化性能。更多详情可查阅手册mantree。
linux命令—tree
|
25天前
|
Unix Linux
linux命令—cd
`cd` 命令是 Linux/Unix 系统中用于切换工作目录的基础命令。支持相对路径与绝对路径,常用选项如 `-L` 和 `-P` 分别处理符号链接的逻辑与物理路径。实际操作中,可通过 `cd ..` 返回上级目录、`cd ~` 回到家目录,或利用 `cd -` 在最近两个目录间快速切换。结合 Tab 补全和 `pwd` 查看当前路径,能显著提升效率。此外,需注意特殊字符路径的正确引用及脚本中绝对路径的优先使用。
|
16天前
|
Linux
Linux命令拓展:为cp和mv添加进度显示
好了,就这样,让你的Linux复制体验充满乐趣吧!记住,每一个冷冰冰的命令背后,都有方法让它变得热情起来。
49 8

热门文章

最新文章