java Freemarker+HTML生成PDF证书

java Freemarker+HTML生成PDF证书

需要生成的证书是这样的:
在这里插入图片描述

1.需要用到的maven包

<!--证书生成开始-->
        <!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.11</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker -->
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.11</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.19</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf -->
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.1.5</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf-itext5 -->
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf-itext5</artifactId>
            <version>9.1.5</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.7</version>
        </dependency>

        <!--证书生成结束-->

2.pom.xml中写入静态文件路径

静态文件是放在 下列路径中

由于我的需要生成的PDF文件中的字体用到了楷体、黑体等字体,所以需要将这些文件引入到程序中
在这里插入图片描述

<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.html</include>
                    <include>**/*.ttf</include>
                    <include>**/*.TTF</include>
                    <include>**/*.ttc</include>
                </includes>
                <!--是否替换资源中的属性-->
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

3.生成文件工具类

本地测试时,我文件放的位置是在D:/netplan/upload/目录下

测试环境和生产环境下,我文件放的位置是/opt/upload/下

你的html文件必须要放在你指定的路径 D:/netplan/upload/templateHtml/下

import com.itextpdf.text.pdf.BaseFont;
import com.lowagie.text.DocumentException;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import gc.cnnvd.framework.util.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.CharEncoding;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;

/**
 * Html生成PDF文件
 */
@Slf4j
public class HtmlToPDFUtils {
    
    

    /**
     * 模板存放位置 /opt/upload/templateHtml/
     */
    private static final String TEMPLATE_HTML_PATH = "D:/netplan/upload/templateHtml/";

    /**
     * 技术支撑单位 certificate.html
     */
    private static final String HTML_NAME = "compatibilityCertificate.html";

    /**
     * 临时HTML文件存放位置 /opt/upload/tempHtml/yyyy-MM-dd/时间戳.html
     */
    private static final String TEMP_HTML_PATH = "D:/netplan/upload/templateHtml/";

    /**
     * 黑体
     */
    private static final String SIMHEI = "fonts/simhei.ttf";
    /**
     * 楷体
     */
    private static final String SIMKAI = "fonts/simkai.ttf";
    /**
     * 隶书
     */
    private static final String SIMLI = "fonts/SIMLI.TTF";
    /**
     * 宋体
     */
    private static final String SIMSUN = "fonts/simsun.ttc";

    /**
     * 渲染HTML文件并生成PDF文件
     *
     * @param params     传递到模板的参数
     * @param targetFile 目标文件的地址
     */
    public static void createPdfFile(Map<String, Object> params, String targetFile) {
    
    
        Template template = null;
        String renderResult = "";
        //创建fm的配置
        Configuration config = new Configuration();
        //指定默认编码格式
        config.setDefaultEncoding(CharEncoding.UTF_8);
        try {
    
    
            config.setDirectoryForTemplateLoading(new File(TEMPLATE_HTML_PATH));
            template = config.getTemplate(HTML_NAME, CharEncoding.UTF_8);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        String outTempHtmlFilePath = TEMP_HTML_PATH + LocalDate.now().toString() + "/" + System.currentTimeMillis() + ".html";
        File writeFile = new File(outTempHtmlFilePath);
        File fileParent = writeFile.getParentFile();
        if (!fileParent.exists()) {
    
    
            fileParent.mkdirs();
        }
        try {
    
    
            writeFile.createNewFile();
            Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(writeFile), CharEncoding.UTF_8));
            template.process(params, writer);
            FileInputStream inputStream = new FileInputStream(outTempHtmlFilePath);
            int length = inputStream.available();
            byte bytes[] = new byte[length];
            inputStream.read(bytes);
            inputStream.close();
            renderResult = new String(bytes, StandardCharsets.UTF_8);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (TemplateException e) {
    
    
            e.printStackTrace();
        }
        try {
    
    
            createPdf(renderResult, targetFile);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (DocumentException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void createPdf(String content, String dest) throws IOException, com.lowagie.text.DocumentException {
    
    
        log.info("-----------------------content-----------------------");
        log.info(content);
        log.info("-----------------------content-----------------------");
        ITextRenderer renderer = new ITextRenderer();
        SharedContext sharedContext = renderer.getSharedContext();
        // 解决base64图片支持问题
        sharedContext.setReplacedElementFactory(new Base64ImgReplacedElementFactory());
        sharedContext.getTextRenderer().setSmoothingThreshold(0);

        ITextFontResolver fontResolver = renderer.getFontResolver();
        fontResolver.addFont(SIMHEI, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        fontResolver.addFont(SIMLI, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        fontResolver.addFont(SIMKAI, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        fontResolver.addFont(SIMSUN, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        // 解析html生成pdf
        renderer.setDocumentFromString(content);
        //解决图片相对路径的问题
        renderer.layout();
        renderer.createPDF(new FileOutputStream(dest));
    }


    public static void main(String[] args) {
    
    
        String outFilePath = "D:/netplan/" + UUIDUtil.getUuid() + ".pdf";
        Map<String, Object> data = new HashMap();
        data.put("certificateCode", "CNNVD-XX-XXXXXX");
        data.put("companyName", "北京XXXXXX有限公司");
        data.put("registerAddress", "北京市海淀区XXXXXXXXXXX");
        data.put("creditCode", "XXXXXXXXXXXXXXXX");
        data.put("productName", "测试产品名称");
        data.put("productVersion", "V1.03");
        data.put("productModel", "SKPI2000XXX");
        data.put("reviewTime", "2021年06月25日");
        data.put("expiryTime", "2022年06月25日");
        data.put("currentTime", "2021年08月06日");

        createPdfFile(data, outFilePath);
    }

}

图片工具类

import com.lowagie.text.BadElementException;
import com.lowagie.text.Image;
import com.lowagie.text.pdf.codec.Base64;
import org.w3c.dom.Element;
import org.xhtmlrenderer.extend.FSImage;
import org.xhtmlrenderer.extend.ReplacedElement;
import org.xhtmlrenderer.extend.ReplacedElementFactory;
import org.xhtmlrenderer.extend.UserAgentCallback;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.pdf.ITextFSImage;
import org.xhtmlrenderer.pdf.ITextImageElement;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;

import java.io.IOException;

/**
 *  * 图片base64支持,把图片转换为itext自己的图片对象
 *  * @author Administrator
 *  *
 *  
 */
public class Base64ImgReplacedElementFactory implements ReplacedElementFactory {
    
    

    /**
     * 实现createReplacedElement 替换html中的Img标签
     *
     * @param c         上下文
     * @param box       盒子
     * @param uac       回调
     * @param cssWidth  css宽
     * @param cssHeight css高
     * @return ReplacedElement
     */
    @Override
    public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac,
                                                 int cssWidth, int cssHeight) {
    
    
        Element e = box.getElement();
        if (e == null) {
    
    
            return null;
        }
        String nodeName = e.getNodeName();
        // 找到img标签
        if (nodeName.equals("img")) {
    
    
            String attribute = e.getAttribute("src");
            FSImage fsImage;
            try {
    
    
                // 生成itext图像
                fsImage = buildImage(attribute, uac);
            } catch (BadElementException e1) {
    
    
                fsImage = null;
            } catch (IOException e1) {
    
    
                fsImage = null;
            }
            if (fsImage != null) {
    
    
                // 对图像进行缩放
                if (cssWidth != -1 || cssHeight != -1) {
    
    
                    fsImage.scale(cssWidth, cssHeight);
                }
                return new ITextImageElement(fsImage);
            }
        }

        return null;
    }

    /**
     * 将base64编码解码并生成itext图像
     *
     * @param srcAttr 属性
     * @param uac     回调
     * @return FSImage
     * @throws IOException         io异常
     * @throws BadElementException BadElementException
     */
    protected FSImage buildImage(String srcAttr, UserAgentCallback uac) throws IOException,
            BadElementException {
    
    
        FSImage fsImage;
        if (srcAttr.startsWith("data:image/")) {
    
    
            String b64encoded = srcAttr.substring(srcAttr.indexOf("base64,") + "base64,".length(),
                    srcAttr.length());
            // 解码
            byte[] decodedBytes = Base64.decode(b64encoded);
            fsImage = new ITextFSImage(Image.getInstance(decodedBytes));
        } else {
    
    
            fsImage = uac.getImageResource(srcAttr).getImage();
        }
        return fsImage;
    }


    /**
     * 实现remove
     *
     * @param e 元素
     */
    @Override
    public void remove(Element e) {
    
    
    }

    /**
     * 实现reset
     */
    @Override
    public void reset() {
    
    
    }

    /**
     * 实现setFormSubmissionListener
     *
     * @param formsubmissionlistener 监听
     */
    @Override
    public void setFormSubmissionListener(FormSubmissionListener formsubmissionlistener) {
    
    
    }
}

4.html证书模板

其中图片需要使用到的是绝对路径

当前图片展示的是本地的路径 在服务器上运行的话需换成服务器上的路径

还有需要注意生成文件的大小 在HTML文件的@page中,当前我的纸张大小为A4纸

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<title>兼容性服务证书</title>
		<style>
			@page {
      
      
				size: a4
			}

			* {
      
      
				margin: 0;
				padding: 0;
				list-style: none;
			}

			.main {
      
      
				background-color: #fffbc6;
				width: 700px;
				height: 1000px;
				position: relative;
			}

			.main .mainin {
      
      
				margin: auto;
				background-color: #fffbc6;
				background: url("file:///D:/netplan/images/border.png") no-repeat;
				background-size: 640px 940px;
				width: 640px;
				height: 940px;
				text-align: center;
				font-family: 'NSimSun';
				position: relative;
				top: 30px;
			}

			.main .mainin img {
      
      
				margin: 70px 0 40px 0;
			}

			.main .mainin .p1 {
      
      
				font: normal small-caps normal 38px/45px 'LiSu';
			}

			.main .mainin .p2 {
      
      
				font: normal small-caps normal 50px/55px 'LiSu';
			}

			.main .mainin .certificate-code {
      
      
				letter-spacing: 5px;
				font: normal small-caps normal 18px/80px 'SimHei';
			}

			.main .mainin .company-name {
      
      
				letter-spacing: 5px;
				font: normal small-caps normal 28px/40px 'SimHei';
			}

			.main .mainin .company-info {
      
      
				margin-top: 10px;
				letter-spacing: 2px;
				font-size: 18px;
			}

			.main .mainin .company-info span::after {
      
      
				content: '';
				display: inline-block;
				width: 100%;
			}


			.main .mainin .company-info span {
      
      
				font: normal small-caps normal 16px/40px 'SimHei';
			}

			.main .mainin .peoduct-info {
      
      
				letter-spacing: 5px;
				margin-top: 40px;
				font: normal small-caps normal 28px/40px 'SimHei';
			}

			.main .mainin .peoduct-version {
      
      
				letter-spacing: 5px;
				font: normal small-caps normal 28px/40px 'SimHei';
			}

			.main .mainin .instruction {
      
      
				font: normal small-caps normal 20px/55px 'SimHei';
			}

			.main .mainin .date-info {
      
      
				font-family: 'NSimSun';
				margin-top: 40px;
			}

			.main .mainin .date-info {
      
      
				font: normal small-caps normal 18px/35px 'SimHei';
			}
			
			.main .mainin .date-info span {
      
      
				letter-spacing: 10px;
			}

			.main .mainin .approve-info {
      
      
				margin-top: 40px;
			}

			.main .mainin .approve-info div {
      
      
				text-align: right;
				margin-right: 100px;
				font: normal small-caps normal 18px/35px 'SimHei';
			}
		</style>
	</head>
	<body>
		<div class="main">
			<div class="mainin">
				<img src="file:///D:/netplan/images/certificateLogo.png" width="150" height="150" />
				<p class="p1">国家信息安全漏洞库</p>
				<p class="p2">兼容性资质证书</p>
				<p class="certificate-code"><span>证书号:</span>${certificateCode}</p>
				<p class="company-name">${companyName}</p>
				<div class="company-info">
					(${registerAddress}<br />
					统一社会信用代码:${creditCode})
				</div>
				<p class="peoduct-info">${productName}</p>
				<p class="peoduct-version">(${productVersion}/ ${productModel})</p>

				<p class="instruction">此证书仅适用于该产品/系统的上述版本和型号</p>

				<div class="date-info">
					<span>签发日期</span>:${reviewTime}<br />
					<span>有效期至</span>:${expiryTime}
				</div>

				<div class="approve-info">
					<div>${currentTime}</div>
				</div>
			</div>
		</div>
	</body>
</html>

猜你喜欢

转载自blog.csdn.net/weixin_43484014/article/details/119946053