freemarker生成html、html转pdf、pdf根据关键字定位、pdf签名图片
PdfUtil 类 (全部功能代码)
package com.wei.weitest.pdf;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.itextpdf.awt.geom.Rectangle2D.Float;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.PrivateKeySignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* <p>Title: PdfUtil</p>
* <p>Description: freemarker生成html 并转pdf 并根据关键字定位 并签名 工具类</p>
* @author weisibin
* @date 2018年11月23日
*/
public class PdfUtil {
private static Configuration config = null;
static{
try {
/**
* Static initialization.
* Initialize the configuration of Freemarker.
*/
config = new Configuration();
//ftl模板文件统一放至基于类路径加载模板
config.setClassForTemplateLoading(PdfUtil.class, "/com/wei/weitest/template");
config.setTemplateUpdateDelay(0);
// 设置编码
config.setDefaultEncoding("UTF-8");
//签名证书 基于BC实现的(JAVA版)SM2国密算法签名 //使用BouncyCastle
Security.addProvider(new BouncyCastleProvider());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* <p>Title: createPdf</p>
* <p>Description: 用freemarker模板 创建pdf</p>
* @author weisibin
* @date 2018年11月23日
* @param dataMap 传入 模板参数
* @param templateName 模板文件名(带后缀)
* @param pdffile 创建的pdf存放路径
* @throws IOException
* @throws TemplateException
* @throws DocumentException
*/
public static void createPdf(Map<String, Object> dataMap, String templateName, String pdffile)
throws IOException, TemplateException, DocumentException {
StringWriter stringWriter = null;
BufferedWriter writer = null;
// 创建配置实例
// 获取模板
Template template = config.getTemplate(templateName);
stringWriter = new StringWriter();
writer = new BufferedWriter(stringWriter);
template.process(dataMap, writer);
String htmlStr = stringWriter.toString();
writer.flush();
writer.close();
// html转 pdf
parseHTML2PDFFile2(pdffile, htmlStr);
}
/**
* <p>Title: parseHTML2PDFFile2</p>
* <p>Description: html字符串转为pdf文件</p>
* @author weisibin
* @date 2018年11月23日
* @param pdfFile
* @param html
* @throws DocumentException
* @throws IOException
*/
private static void parseHTML2PDFFile2(String pdfFile, String html) throws DocumentException, IOException {
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(pdfFile));
// HTML转PDF
document.open();
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes()),
Charset.forName("UTF-8"), new XMLWorkerFontProvider() {
//pdf中文字体解决
public Font getFont(final String fontname, final String encoding, final boolean embedded,
final float size, final int style, final BaseColor color) {
BaseFont bf = null;
try {
bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
e.printStackTrace();
}
Font font = new Font(bf, size, style, color);
font.setColor(color);
return font;
}
});
document.close();
}
/**
* <p>Title: signPdf</p>
* <p>Description: pdf签名证书</p>
* @author weisibin
* @date 2018年11月23日
* @param src 签名pdf文件位置
* @param os 签名后pdf输出流
* @param map 需要传递的参数
* @throws Exception
*/
public static void signPdf(String src, OutputStream os ,Map<String,Object> map) throws Exception {
KeyStore ks = KeyStore.getInstance("pkcs12", "BC");
// 证书与证书密码
ks.load(new FileInputStream(Content.PFX_PATH), Content.PFX_PASS.toCharArray());
String alias = (String) ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, Content.PFX_PASS.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
// 读取需要签名的PDF源文件
PdfReader reader = new PdfReader(src);
// 创建签章工具PdfStamper
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
// 获取数字签章属性对象
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
//签名原因
if(null != map.get("reason")){
appearance.setReason(String.valueOf(map.get("reason")));
}
//签名位置
if(null != map.get("location")){
appearance.setLocation(String.valueOf(map.get("location")));
}
// 获取pdf的最后一页页数
int endPages = reader.getNumberOfPages();
// 获得读取pdf内容对象
PdfReaderContentParser parse = new PdfReaderContentParser(reader);
// 获得相关页码关键字具体位置 对象
//关键字 key
String keyWords = "某某签章:";
List<float[]> pl = getKeyWordPositionByPageNum(parse, endPages, keyWords);
// 签章位置
float[] p = null;
// 获取最后一个关键字定位对象
if (pl != null && pl.size() != 0) {
p = pl.get(pl.size() - 1);
} else {
throw new Exception("pdf签章没有读取到签章关键字!");
}
// 设置签名签名域图章大小及位置
Rectangle positon = new Rectangle(p[0] + 85, p[1] - 80, p[0] + 165, p[1]);
// 设置签名的签名域名称
appearance.setVisibleSignature(positon, endPages, "sign");
// 读取图章图片,这个image是itext包的image
Image image = Image.getInstance(Content.LOGO_PATH);
appearance.setSignatureGraphic(image);
appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
// 设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
appearance.setRenderingMode(RenderingMode.GRAPHIC);
// 签名算法
ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", "BC");
// 摘要算法
ExternalDigest digest = new BouncyCastleDigest();
// 调用itext签名方法完成pdf签章
MakeSignature.signDetached(appearance, digest, es, chain, null, null, null, 0, CryptoStandard.CMS);
}
/**
* <p>Title: signPdf</p>
* <p>Description: pdf签名证书</p>
* @author weisibin
* @date 2018年11月23日
* @param src 签名pdf文件位置
* @param filepath 签名后pdf文件存放位置
* @param map 需要传入的参数
* @throws Exception
*/
public static void signPdf(String src, String filepath ,Map<String,Object> map) throws Exception{
OutputStream os = new FileOutputStream(filepath);
signPdf(src, os ,map);
}
/**
* <p>Title: getKeyWordPosition</p>
* <p>Description: 根据关键字和页码 获取关键字的定位</p>
* @author weisibin
* @date 2018年11月23日
* @param parse 读取pdf内容对象
* @param pageNumber 定位的页码
* @param keyword 关键字
* @return List<float[]> 定位集合 float[]:坐标和页数 float[0] >> X float[1] >> Y float[2] >> page
* @throws Exception
*/
private static List<float []> getKeyWordPositionByPageNum(PdfReaderContentParser parse,Integer pageNumber, String keyword) throws Exception {
List<float []> list = new ArrayList<float []>();
float [] resu = new float[3];
parse.processContent(pageNumber, new RenderListener() {
@Override
public void renderText(TextRenderInfo renderInfo) {
String text = renderInfo.getText();
if (null != text && text.contains(keyword)) {
Float boundingRectange = renderInfo.getBaseline().getBoundingRectange();
resu[0] = boundingRectange.x;
resu[1] = boundingRectange.y;
resu[2] = pageNumber;
list.add(resu);
}
}
@Override
public void beginTextBlock() {
}
@Override
public void endTextBlock() {
}
@Override
public void renderImage(ImageRenderInfo renderInfo) {
}
});
return list;
}
/**
* <p>Title: getKeyWordPositionByAllPages</p>
* <p>Description: 根据keyword 全局定位</p>
* @author weisibin
* @date 2018年11月23日
* @param parse 读取pdf内容对象
* @param pages pdf总页数
* @param keyword 关键字
* @return List<float[]> 定位集合 float[]:坐标和页数 float[0] >> X float[1] >> Y float[2] >> page
* @throws Exception
*/
public static List<float[]> getKeyWordPositionByAllPages(PdfReaderContentParser parse, Integer pages, String keyword)
throws Exception {
List<float[]> List = new ArrayList<float[]>();
List<float[]> List1 = null;
for (int pageNumber = 1; pageNumber <= pages; pageNumber++) {
List1 = getKeyWordPositionByPageNum(parse,pageNumber, keyword);
if (List1 != null && List1.size() != 0) {
List.addAll(List1);
}
}
return List;
}
}
Content 类 (自己创建相关文件)
package com.wei.weitest.pdf;
/**
* <p>Title: Content</p>
* <p>Description: pdf配置类</p>
* @author weisibin
* @date 2018年11月23日
*/
public class Content {
public static String BASE = "D:/dddd/pdfSign"; //文件存放路径
public static String PDF_NEW = BASE + "/haveSgin.pdf"; // 创建的PDF
public static String PDF_SIGNED = BASE + "/haveSgin_1.pdf"; // 签名的文件
public static final String PFX_PATH = BASE + "/fake-credentials.pfx";// 证书,包含密钥
public static final String PFX_PASS = "1111111"; // 这是签名证书的密码
public static final String LOGO_PATH = BASE + "/logo.png"; // 签名的图
}
相关依赖(基于spring-boot测试)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wei</groupId>
<artifactId>weitest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>weitest</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId>
</dependency> -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<groupId>org.xhtmlrenderer</groupId>
<version>9.0.6</version>
</dependency>
<dependency>
<artifactId>freemarker</artifactId>
<groupId>freemarker</groupId>
<version>2.3.8</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.56</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-pdfa</artifactId>
<version>5.5.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-ext-jdk16 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk16</artifactId>
<version>1.46</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-xtra</artifactId>
<version>5.5.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcmail-jdk16 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk16</artifactId>
<version>1.46</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bctsp-jdk16 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctsp-jdk16</artifactId>
<version>1.46</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-scratchpad -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.16-beta2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.7-beta3</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>