zoukankan      html  css  js  c++  java
  • HTML 转 PDF

    几种HTML转PDF工具的对比

    工具 特点
    html2image 简单html转化,对CSS的支持不好
    itextpdf 需要自己写模板,可以动态填充
    wkhtmltopdf 转化速度快,效果好

    所以此处我们重点将wkhtmltopdf的使用做一个示例,完整的项目地址在末尾的链接处

    使用

    springboot是现在开发的主流框架,所以此处主要是示例在springboot项目中如何集成,其他项目请自行参考使用

    准备

    需要准备三个基础的文件,分别如下:

    • simsun.ttc:字体文件
    • wkhtmltopdf.exe:转换工具,window系统下使用,适用于64为系统,32位系统自行去官网下载对应版本
    • wkhtmltox:转换工具,Linux系统下使用,同样适用于64位系统

    将以上三个文件拷贝到springboot的resources根目录,具体的文件可到文章末尾的项目地址链接中获取,如下图:

    pom依赖

    <!-- lombok -->
    <dependency>
    	<groupId>org.projectlombok</groupId>
    	<artifactId>lombok</artifactId>
    	<version>1.18.12</version>
    	<scope>provided</scope>
    </dependency>
    <!-- junit -->
    <dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
    	<version>4.12</version>
    	<scope>test</scope>
    </dependency>
    <!-- commons-fileupload -->
    <dependency>
    	<groupId>commons-fileupload</groupId>
    	<artifactId>commons-fileupload</artifactId>
    	<version>1.3.1</version>
    </dependency>
    <!-- 获取系统信息 -->
    <dependency>
    	<groupId>net.java.dev.jna</groupId>
    	<artifactId>jna</artifactId>
    	<version>5.4.0</version>
    </dependency>
    
    <!-- 好用的工具类 -->
    <dependency>
    	<groupId>cn.hutool</groupId>
    	<artifactId>hutool-all</artifactId>
    	<version>5.4.0</version>
    </dependency>
    

    新建工具类

    import com.sun.jna.Platform;
    
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.FileItemHeaders;
    import org.apache.commons.fileupload.util.FileItemHeadersImpl;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.LineNumberReader;
    import java.io.OutputStream;
    import java.io.UnsupportedEncodingException;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.nio.file.StandardCopyOption;
    import java.nio.file.StandardOpenOption;
    import java.util.Arrays;
    
    import cn.hutool.core.io.FileUtil;
    import lombok.ToString;
    import lombok.extern.slf4j.Slf4j;
    
    /**
      * Html转PDF的工具类
      * @author zhongyj <1126834403@qq.com><br/>
      * @date 2020/8/29
      */
    @Slf4j
    public class Html2PdfUtils {
    
        private static final File WK_HOME_DIR = FileUtil.file(FileUtil.getUserHomePath()+"/wkHome");
    
        private static final File WK_TMP_DIR = FileUtil.file(FileUtil.getTmpDirPath()+"/wkTemp");
    
        private static final File SIM_SUN_FONT_DIR = Platform.isLinux() ? FileUtil.file("/usr/share/fonts/chinese/TrueType")
                : FileUtil.file("C:\Windows\Fonts");
    
        private static File wkTool;
        private static File simSunFont;
    
        private static boolean canUse = true;
        private static boolean able = true;
    
    
        static {
            log.info("Tools are only  available for Windows 64 and Linux 64 platforms !!!");
            boolean init = forceInit();
            log.info("Tools init result: {}", init);
        }
    
        /**
         * 初始化
         * @return  是否初始化成功
         */
        public static boolean forceInit() {
            long initc = 0L;
            if (!WK_HOME_DIR.exists()) {
                able = WK_HOME_DIR.mkdirs();
            }
            log.info("{},check wkHomeDir ,result:{}", ++initc, able);
            if (!WK_TMP_DIR.exists()) {
                able = WK_TMP_DIR.mkdirs();
            }
            log.info("{},check wkTmpDir ,result:{}", ++initc, able);
    
            if (!SIM_SUN_FONT_DIR.exists()) {
                able = SIM_SUN_FONT_DIR.mkdirs();
            }
            log.info("{},check simsunFontDir ,result:{}", ++initc, able);
    
            InputStream wkHtmlToxAsStream = null;
            InputStream simSunAsStream = null;
            if (able) {
                wkHtmlToxAsStream = Platform.isLinux() ? Html2PdfUtils.class.getResourceAsStream("/wkhtmltox") : Html2PdfUtils.class.getResourceAsStream("/wkhtmltopdf.exe");
                simSunAsStream = Html2PdfUtils.class.getResourceAsStream("/simsun.ttc");
            }
            if (null == wkHtmlToxAsStream || simSunAsStream == null) {
                log.error("{},load wkHtmlToxAsStream :{},load simSunAsStream:{}", ++initc, null == wkHtmlToxAsStream, simSunAsStream == null);
                able = false;
            }
            log.info("{},load wktool and font source ,result:{}", ++initc, able);
    
            if (able) {
                File font = new File(SIM_SUN_FONT_DIR, "simsun.ttc");
                File wk = new File(WK_HOME_DIR, Platform.isLinux() ? "wkhtmltox" : "wkhtmltopdf.exe");
    
                try {
                    if (!font.exists()) {
                        assert simSunAsStream != null;
                        able = 1 < Files.copy(simSunAsStream, font.toPath(), StandardCopyOption.REPLACE_EXISTING);
                    }
                    log.info("{},copy font source to {},result:{}", ++initc, font.toPath(), able);
                    if (!wk.exists()) {
                        assert wkHtmlToxAsStream != null;
                        able = 1 < Files.copy(wkHtmlToxAsStream, wk.toPath(), StandardCopyOption.REPLACE_EXISTING);
                    }
                    log.info("{},copy wktools source to {},result:{}", ++initc, font.toPath(), able);
    
                    if (able) {
                        wkTool = wk;
                        simSunFont = font;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    able = false;
                    log.error("{}, error when copy source : {} ", ++initc, e.getMessage());
                }
            }
    
            if (able) {
                if (Platform.isLinux()) {
                    boolean canExe = exePermissionCheck();
                    log.info("{},check run permission,result: {}  ", ++initc, canExe ? "has permission" : "no permission");
                    if (!canExe) {
                        simpleExecCommand("chmod +x " + wkTool.getPath());
                        if (!exePermissionCheck()) {
                            log.error("{},add permission failed", ++initc);
                            able = false;
                        }
                    }
                }
            }
            if (able) {
                able = cleanTempDir();
            }
            if (able) {
                log.info("{},init success!", ++initc);
            } else {
                log.info("{},init failed!", ++initc);
                canUse = false;
            }
            return able;
        }
    
        public String getSimsunPath() {
            log.info("world path:" + simSunFont.getPath());
            return simSunFont.getPath();
        }
    
        public static Html2PdfUtils build() {
            return new Html2PdfUtils();
        }
    
        private static boolean exePermissionCheck() {
            String permissionLog = simpleExecCommand("ls -l " + wkTool.getPath());
            return null != permissionLog && permissionLog.length() >= 10 && 120 == permissionLog.charAt(9);
        }
    
        private static boolean cleanTempDir() {
            if (WK_TMP_DIR.exists()) {
                canUse = deleteFiles(WK_TMP_DIR) ? WK_TMP_DIR.mkdirs() : canUse;
                log.info("cleanTempDir,result:{} ", canUse);
            } else {
                canUse = WK_TMP_DIR.mkdirs();
            }
            return canUse;
        }
    
        public synchronized FileItem convertPdfFromText(String text, String fileName) {
            cleanTempDir();
            if (!canUse) {
                log.info("tools crash,can invoke forceInit() method see reason !!!");
                return null;
            }
            File html = new File(WK_TMP_DIR, fileName + ".html");
            File pdf = new File(WK_TMP_DIR, fileName + ".pdf");
    
            // 将html字符串写入到临时的html文件
            FileUtil.writeUtf8String(text, html);
    
            if (html.exists() && html.isFile()) {
                log.info("exec html to  pdf ,wktoolPath=>{}", wkTool.getPath());
                simpleExecCommand(wkTool.getPath() + " " + html.getPath() + " " + pdf.getPath());
            }
            if (pdf.exists() && pdf.isFile()) {
                try (FileInputStream fileInputStream = new FileInputStream(pdf); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
                    byte[] buffer = new byte[2014];
                    while (fileInputStream.read(buffer) != -1) {
                        byteArrayOutputStream.write(buffer);
                    }
                    byteArrayOutputStream.flush();
                    byte[] data = byteArrayOutputStream.toByteArray();
                    if (data.length > 0) {
                        log.info("html to  pdf  success ");
                        SimplePdfFileItem file = new SimplePdfFileItem(pdf, data, Files.probeContentType(pdf.toPath()), "file");
                        log.info("pdf size :{}", file.getSize());
                        return file;
                    }
                } catch (IOException e) {
                    log.error("html to  pdf  failed");
                    e.printStackTrace();
                    return null;
                }
            }
            log.info("html to  pdf  failed,no data");
            return null;
        }
    
        private static boolean deleteFiles(File file) {
            if (!file.exists()) {
                log.info("del the file:{},is not exists", file.getPath());
                return false;
            }
            if (file.isFile()) {
                return file.delete();
            }
            File[] subFiles = file.listFiles();
            if (null != subFiles && subFiles.length > 0) {
                Arrays.asList(subFiles).forEach(Html2PdfUtils::deleteFiles);
            }
    
            return file.delete();
        }
    
        private static String simpleExecCommand(String cmd) {
            try {
                String[] linux = {"/bin/sh", "-c", cmd};
                String[] windows = {"cmd", "/c", cmd};
                String[] cmdA = Platform.isLinux() ? linux : windows;
                Process process = Runtime.getRuntime().exec(cmdA);
                LineNumberReader br = new LineNumberReader(new InputStreamReader(process.getInputStream()));
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) {
                    log.info(line);
                    sb.append(line).append("
    ");
                }
                return sb.toString();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        @ToString
        static class SimplePdfFileItem implements FileItem {
            private static final long serialVersionUID = 2237570099615271025L;
            public static final String DEFAULT_CHARSET = "ISO-8859-1";
            private String fieldName;
            private final String fileName;
            private boolean isFormField;
            private final byte[] cachedContent;
            private final String contentType;
            private final File dFosFile;
            private FileItemHeaders headers;
    
            public SimplePdfFileItem(File dFosFile, byte[] cachedContent, String contentType, String fieldName) {
                this.fieldName = fieldName;
                this.fileName = dFosFile.getName();
                this.isFormField = false;
                this.cachedContent = null == cachedContent || cachedContent.length < 1 ? new byte[0] : cachedContent;
                this.contentType = contentType;
                this.dFosFile = dFosFile;
                this.headers = new FileItemHeadersImpl();
            }
    
            public SimplePdfFileItem(String fieldName, String fileName, boolean isFormField, byte[] cachedContent
                    , String contentType, File dFosFile, FileItemHeaders headers) {
                this.fieldName = fieldName;
                this.fileName = fileName;
                this.isFormField = isFormField;
                this.cachedContent = cachedContent;
                this.contentType = contentType;
                this.dFosFile = dFosFile;
                this.headers = headers;
            }
    
            @Override
            public InputStream getInputStream() throws IOException {
                if (null == this.dFosFile) {
                    return new ByteArrayInputStream(this.cachedContent);
                }
                return new FileInputStream(dFosFile);
            }
    
            @Override
            public String getContentType() {
                return this.contentType;
            }
    
            @Override
            public String getName() {
                return this.fileName;
            }
    
            @Override
            public boolean isInMemory() {
                return this.cachedContent.length > 0;
            }
    
            @Override
            public long getSize() {
                return this.cachedContent.length;
            }
    
            @Override
            public byte[] get() {
                return this.cachedContent;
            }
    
            @Override
            public String getString(String s) throws UnsupportedEncodingException {
                return getString();
            }
    
            @Override
            public String getString() {
                return new String(cachedContent, StandardCharsets.UTF_8);
            }
    
            @Override
            public void write(File file) throws Exception {
                Files.write(file.toPath(), cachedContent, StandardOpenOption.CREATE);
            }
    
            @Override
            public void delete() {
                boolean delete = dFosFile.delete();
            }
    
            @Override
            public String getFieldName() {
                return this.fieldName;
            }
    
            @Override
            public void setFieldName(String s) {
                this.fieldName = s;
            }
    
            @Override
            public boolean isFormField() {
                return this.isFormField;
            }
    
            @Override
            public void setFormField(boolean b) {
                this.isFormField = b;
            }
    
            @Override
            public OutputStream getOutputStream() throws IOException {
                if (null == this.dFosFile) {
                    return new ByteArrayOutputStream(1024);
                }
                return new FileOutputStream(this.dFosFile);
            }
    
            @Override
            public FileItemHeaders getHeaders() {
                return this.headers;
            }
    
            @Override
            public void setHeaders(FileItemHeaders fileItemHeaders) {
                this.headers = fileItemHeaders;
            }
    
        }
    
    }
    

    转换

    @Test
    public void down() throws UnsupportedEncodingException {
        String html = FileUtil.readUtf8String("E:\入院记录.html");
        FileItem sx = Html2PdfUtils.build().convertPdfFromText(html, "sx");
        log.info(sx.toString());
        byte[] bytes = sx.get();
        FileUtil.writeBytes(bytes,new File("E:\入院记录-1.pdf"));
    }
    

    项目示例地址:https://gitee.com/dimples9527/html2pdf

  • 相关阅读:
    PHP对象
    MySQL多表更新
    使用not in的子查询
    MySQL比较运算符的子查询
    控制器调用函数
    MVC目录规范
    MVC流程
    mxnet安装
    离线安装Python包hickle,easydict
    深度学习基础
  • 原文地址:https://www.cnblogs.com/reroyalup/p/13589603.html
Copyright © 2011-2022 走看看