環(huán)境準(zhǔn)備
開(kāi)發(fā)環(huán)境
java8,SpringBoot 2.1.4,字符集GBK
字體
宋體--simsun.ttf
pom依賴(lài)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>7.0.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>io</artifactId>
<version>7.0.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>forms</artifactId>
<version>7.0.3</version>
</dependency>
<!-- 解決中文字體問(wèn)題 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.0.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.13</version>
</dependency>
模板生成PDF
將模板轉(zhuǎn)換為html
從文件讀取模板
/**
* 初始化freemarker配置
* templateRoot:模板文件根目錄
*/
Configuration freemarkerCfg = initFreemarkerCfg(templateRoot);
/**
* 將模板轉(zhuǎn)換為HTML字符串
*/
String content = freeMarkerRender(data, freemarkerCfg, htmlTemplate, charSet);
private static Configuration initFreemarkerCfg(String templateRoot) {
Configuration freemarkerCfg = new Configuration();
try {
freemarkerCfg.setDirectoryForTemplateLoading(new File(templateRoot));
} catch (IOException e) {
log.error("模板根路徑獲取失敗!" + templateRoot, e);
throw new RuntimeException("模板根路徑獲取失敗!" + templateRoot,e);
}
return freemarkerCfg;
}
/**
* data 需要注入模板的數(shù)據(jù)
* freemarkerCfg freemarker配置
* htmlTmp 模板名稱(chēng)
* charSet 字符集 linux下使用UTF-8,windows下使用GBK,否則會(huì)出現(xiàn)中文亂碼,模板文件的文件編碼和聲明編碼同樣需要保持一致
*/
private static String freeMarkerRender(Map<String, Object> data, Configuration freemarkerCfg, String htmlTmp,String charSet) {
try (Writer out = new StringWriter();) {
Template template = freemarkerCfg.getTemplate(htmlTmp);
template.setEncoding(charSet);
template.process(data, out);
out.flush();
return out.toString();
} catch (Exception e) {
log.error("HTML加載數(shù)據(jù)失敗!", e);
throw new RuntimeException("HTML加載數(shù)據(jù)失敗!", e);
}
}
從流讀取模板
/**
* data 需要注入模板的數(shù)據(jù)
* fileName 文件名稱(chēng)
* inputStream 模板文件流
* charSet 字符集 linux下使用UTF-8,windows下使用GBK,否則會(huì)出現(xiàn)中文亂碼,模板文件的文件編碼和聲明編碼同樣需要保持一致
*/
private static String freeMarkerRender(Map<String, Object> data, String fileName, InputStream inputStream,
String charSet) {
try (Writer out = new StringWriter();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);) {
Configuration configuration = new Configuration();
Template template = new Template(fileName, inputStreamReader, configuration);
template.setEncoding(charSet);
template.process(data, out);
out.flush();
return out.toString();
} catch (Exception e) {
log.debug("HTML加載數(shù)據(jù)失敗!", e);
throw new RuleException(ErrCodeFile.CO_HTML_TEMPLATE_CONVERT_ERROR);
}
}
生成PDF
/**
* htmlContent 通過(guò)freemarker生成的html
* fontPath 字體文件路徑
* ByteArrayOutputStream pdf文件流
*/
private static ByteArrayOutputStream htmlToPdf(String htmlContent, String fontPath) {
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ITextRenderer render = new ITextRenderer();
ITextFontResolver fontResolver = render.getFontResolver();
fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
render.setDocumentFromString(htmlContent);
render.getSharedContext().setBaseURL(BASE_URL);
render.layout();
render.createPDF(output);
return output;
} catch (Exception e) {
log.debug("html轉(zhuǎn)換pdf失敗!", e);
throw new RuntimeException("html轉(zhuǎn)換pdf失敗!", e);
}
}
添加水印和頁(yè)碼
從文件獲取水印
/**
* outputStream 生成的pdf流
* waterMarkPath 水印文件路徑
* fontPath 字體路徑
* OutputStream pdf文件流
*/
private static OutputStream addWaterImage(ByteArrayOutputStream outputStream, String waterMarkPath,
String fontPath) {
BaseFont baseFont = createFont(fontPath);
try (InputStream input = new ByteArrayInputStream(outputStream.toByteArray());) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
PdfReader reader = new PdfReader(input);
PdfStamper stamp = new PdfStamper(reader, output);
PdfContentByte contentByte = null;
int n = reader.getNumberOfPages();
Image logo = null;
if(StringUtils.isNotBlank(waterMarkPath)){
logo = Image.getInstance(waterMarkPath);
}
for (int i = 1; i <= n; i++) {
contentByte = stamp.getUnderContent(i);
Rectangle rectangle = reader.getPageSize(i);
float width = rectangle.getWidth();
float height = rectangle.getHeight();
if(logo != null){
logo.setAbsolutePosition(width / 2 - logo.getWidth() / 2, height / 2);
contentByte.addImage(logo);
contentByte.saveState();
}
String text = "第 " + i + " 頁(yè) /共 " + n + " 頁(yè)";
contentByte.beginText();
contentByte.setFontAndSize(baseFont, 12);
contentByte.showTextAligned(Element.ALIGN_CENTER, text, (width / 2) - 6, 15, 0);
contentByte.endText();
}
reader.close();
stamp.close();
return output;
} catch (Exception e) {
log.debug("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
throw new RuntimeException("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
}
}
private static BaseFont createFont(String fontPath) {
try {
return BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
log.debug("字體讀取失敗," + fontPath, e);
throw new RuntimeException("字體讀取失敗," + fontPath, e);
}
}
從流獲取水印
/**
* outputStream 生成的pdf流
* waterMarkPath 水印流
* fontPath 字體路徑
* OutputStream pdf文件流
*/
private static OutputStream addWaterImage(ByteArrayOutputStream outputStream, InputStream waterMarkPath,
String fontPath) {
BaseFont baseFont = createFont(fontPath);
try (InputStream input = new ByteArrayInputStream(outputStream.toByteArray());) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
PdfReader reader = new PdfReader(input);
PdfStamper stamp = new PdfStamper(reader, output);
PdfContentByte contentByte = null;
int n = reader.getNumberOfPages();
Image logo = null;
if(waterMarkPath != null){
byte[] waterMarkBytes = IOUtils.toByteArray(inputStream);
logo = Image.getInstance(waterMarkBytes);
}
for (int i = 1; i <= n; i++) {
contentByte = stamp.getUnderContent(i);
Rectangle rectangle = reader.getPageSize(i);
float width = rectangle.getWidth();
float height = rectangle.getHeight();
if(logo != null){
logo.setAbsolutePosition(width / 2 - logo.getWidth() / 2, height / 2);
contentByte.addImage(logo);
contentByte.saveState();
}
String text = "第 " + i + " 頁(yè) /共 " + n + " 頁(yè)";
contentByte.beginText();
contentByte.setFontAndSize(baseFont, 12);
contentByte.showTextAligned(Element.ALIGN_CENTER, text, (width / 2) - 6, 15, 0);
contentByte.endText();
}
reader.close();
stamp.close();
return output;
} catch (Exception e) {
log.debug("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
throw new RuntimeException("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
}
}
pdf加密碼及權(quán)限設(shè)置
權(quán)限說(shuō)明
| 權(quán)限 | 說(shuō)明 |
|---|---|
| ALLOW_PRINTING | 文檔允許打印 |
| ALLOW_DEGRADED_PRINTING | 允許用戶(hù)打印文檔,但不提供allow_printing質(zhì)量(128位加密) |
| ALLOW_MODIFY_CONTENTS | 允許用戶(hù)修改內(nèi)容,例如 更改頁(yè)面內(nèi)容,或插入或刪除頁(yè) |
| ALLOW_ASSEMBLY | 允許用戶(hù)插入、刪除和旋轉(zhuǎn)頁(yè)面和添加書(shū)簽。頁(yè)面的內(nèi)容不能更改,除非也授予allow_modify_contents權(quán)限,(128位加密) |
| ALLOW_COPY | 允許用戶(hù)復(fù)制或以其他方式從文檔中提取文本和圖形,包括使用輔助技術(shù)。例如屏幕閱讀器或其他可訪問(wèn)設(shè)備 |
| ALLOW_SCREENREADERS | 允許用戶(hù)提取文本和圖形以供易訪問(wèn)性設(shè)備使用,(128位加密) |
| ALLOW_MODIFY_ANNOTATIONS | 允許用戶(hù)添加或修改文本注釋和交互式表單字段 |
| ALLOW_FILL_IN | 允許用戶(hù)填寫(xiě)表單字段,(128位加密) |
需要多個(gè)權(quán)限時(shí),用|拼接即可
無(wú)水印頁(yè)碼
在生成PDF時(shí)添加權(quán)限及密碼
private static ByteArrayOutputStream htmlToPdf(String htmlContent, String fontPath, String password, String adminPassword) {
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ITextRenderer render = new ITextRenderer();
ITextFontResolver fontResolver = render.getFontResolver();
setPDFEncryption(password, adminPassword, render);
fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
render.setDocumentFromString(htmlContent);
render.getSharedContext().setBaseURL(BASE_URL);
render.layout();
render.createPDF(output);
return output;
} catch (Exception e) {
log.debug("html轉(zhuǎn)換pdf失敗!", e);
throw new RuleException(ErrCodeFile.CO_HTML_TO_PDF_FAILED);
}
}
/** 用戶(hù)權(quán)限,根據(jù)需求自己設(shè)置*/
private static final int PERMIT = PdfWriter.ALLOW_PRINTING;
/** pdf加密類(lèi)型*/
private static final int ENCRYPTION_TYPE = PdfWriter.STANDARD_ENCRYPTION_128;
private static void setPDFEncryption(String password, String adminPassword,ITextRenderer render) {
PDFEncryption pdfEncryption = new PDFEncryption();
// 用戶(hù)密碼
pdfEncryption.setUserPassword(password.getBytes());
// 管理員密碼
pdfEncryption.setOwnerPassword(adminPassword.getBytes());
// 用戶(hù)權(quán)限
pdfEncryption.setAllowedPrivileges(PERMIT);
// 加密類(lèi)型
pdfEncryption.setEncryptionType(ENCRYPTION_TYPE);
render.setPDFEncryption(pdfEncryption);
}
有水印頁(yè)碼
在生成PDF時(shí)無(wú)需添加權(quán)限及密碼,在添加水印及頁(yè)碼時(shí)添加
private static OutputStream addWaterImage(ByteArrayOutputStream outputStream, InputStream waterMarkPath,
String fontPath, String password, String adminPassword) {
BaseFont baseFont = createFont(fontPath);
try (InputStream input = new ByteArrayInputStream(outputStream.toByteArray());) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
PdfReader reader = new PdfReader(input);
PdfStamper stamp = new PdfStamper(reader, output);
// 用戶(hù)密碼,管理員密碼,權(quán)限,加密類(lèi)型
stamp.setEncryption(password.getBytes(), adminPassword.getBytes(), PERMIT, ENCRYPTION_TYPE);
PdfContentByte contentByte = null;
int n = reader.getNumberOfPages();
Image logo = null;
if(waterMarkPath != null){
byte[] waterMarkBytes = IOUtils.toByteArray(inputStream);
logo = Image.getInstance(waterMarkBytes);
}
for (int i = 1; i <= n; i++) {
contentByte = stamp.getUnderContent(i);
Rectangle rectangle = reader.getPageSize(i);
float width = rectangle.getWidth();
float height = rectangle.getHeight();
if(logo != null){
logo.setAbsolutePosition(width / 2 - logo.getWidth() / 2, height / 2);
contentByte.addImage(logo);
contentByte.saveState();
}
String text = "第 " + i + " 頁(yè) /共 " + n + " 頁(yè)";
contentByte.beginText();
contentByte.setFontAndSize(baseFont, 12);
contentByte.showTextAligned(Element.ALIGN_CENTER, text, (width / 2) - 6, 15, 0);
contentByte.endText();
}
reader.close();
stamp.close();
return output;
} catch (Exception e) {
log.debug("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
throw new RuntimeException("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
}
}
模板記錄
1、字符集亂碼問(wèn)題
<meta http-equiv="Content-Type" content="text/html; charset=GBK"/>
(歷史原因?qū)е麻_(kāi)發(fā)使用GBK字符集,UTF-8的情況暫時(shí)未知)charset應(yīng)與Java代碼中的傳入的保持一致,同時(shí)ftl文件的字符集應(yīng)與此保持一致,windows使用GBK,linux使用UTF-8,否則生成PDF亂碼
2、img標(biāo)簽
<img src="${logoImage}" width="204"/>
img標(biāo)簽支持base64格式data:image/png;base64,
data:,文本數(shù)據(jù)
data:text/plain,文本數(shù)據(jù)
data:text/html,HTML代碼
data:text/html;base64,base64編碼的HTML代碼
data:text/css,CSS代碼
data:text/css;base64,base64編碼的CSS代碼
data:text/javascript,Javascript代碼
data:text/javascript;base64,base64編碼的Javascript代碼
data:image/gif;base64,base64編碼的gif圖片數(shù)據(jù)
data:image/png;base64,base64編碼的png圖片數(shù)據(jù)
data:image/jpeg;base64,base64編碼的jpeg圖片數(shù)據(jù)
data:image/x-icon;base64,base64編碼的icon圖片數(shù)據(jù)
3、強(qiáng)制分頁(yè)
<p style="margin: 0pt">
<div style="page-break-before: always; clear: both"/>
</p>
4、 head記錄
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK"/>
<meta http-equiv="Content-Style-Type" content="text/css"/>
<title>xxxx</title>
<style type='text/css'>
body {
font-family: SimSun;
padding-top: 50px;
}
@page {
size: a4;
@top-center {
content: element(header);
}
@bottom-center {
content: element(footer);
}
}
div.header {
display: block;
/*text-align: center;*/
position: running(header);
width: 100%;
}
div.footer {
display: block;
text-align: center;
position: running(footer);
width: 100%;
}
.custom-page-start {
margin-top: 50px;
}
table {
border-collapse: collapse;
margin: 0 auto;
width: 100%;
}
td {
border: #000000 solid 0.75pt;
vertical-align: top;
padding: 5pt;
}
p {
line-height: 18pt;
margin: 0pt 0pt 4pt;
}
span {
font-size: 10pt;
}
@media print {
table {
page-break-after: auto
}
tr {
page-break-inside: avoid;
page-break-after: auto
}
td {
page-break-inside: avoid;
page-break-after: auto
}
thead {
display: table-header-group
}
tfoot {
display: table-footer-group
}
}
</style>
</head>