freemarker html生成pdf,freemarker生成PDF文件

该博客介绍了如何在Java SpringBoot环境下,利用Freemarker模板引擎将HTML转换为PDF,并详细阐述了处理字符集GBK、添加水印和页码的步骤,以及PDF加密和权限设置的方法。内容涵盖了模板读取、字体处理、HTML到PDF的转换、PDF加密码等关键操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

环境准备

开发环境

java8,SpringBoot 2.1.4,字符集GBK

字体

宋体--simsun.ttf

pom依赖

org.springframework.boot

spring-boot-starter-freemarker

com.itextpdf

kernel

7.0.3

com.itextpdf

io

7.0.3

com.itextpdf

forms

7.0.3

com.itextpdf

font-asian

7.0.3

com.itextpdf

itextpdf

5.5.13

com.itextpdf.tool

xmlworker

5.5.13

模板生成PDF

将模板转换为html

从文件读取模板

/**

* 初始化freemarker配置

* templateRoot:模板文件根目录

*/

Configuration freemarkerCfg = initFreemarkerCfg(templateRoot);

/**

* 将模板转换为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 需要注入模板的数据

* freemarkerCfg freemarker配置

* htmlTmp 模板名称

* charSet 字符集 linux下使用UTF-8,windows下使用GBK,否则会出现中文乱码,模板文件的文件编码和声明编码同样需要保持一致

*/

private static String freeMarkerRender(Map 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加载数据失败!", e);

throw new RuntimeException("HTML加载数据失败!", e);

}

}

从流读取模板

/**

* data 需要注入模板的数据

* fileName 文件名称

* inputStream 模板文件流

* charSet 字符集 linux下使用UTF-8,windows下使用GBK,否则会出现中文乱码,模板文件的文件编码和声明编码同样需要保持一致

*/

private static String freeMarkerRender(Map 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加载数据失败!", e);

throw new RuleException(ErrCodeFile.CO_HTML_TEMPLATE_CONVERT_ERROR);

}

}

生成PDF

/**

* htmlContent 通过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转换pdf失败!", e);

throw new RuntimeException("html转换pdf失败!", e);

}

}

添加水印和页码

从文件获取水印

/**

* 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 + " 页 /共 " + n + " 页";

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("添加水印和页码失败," + waterMarkPath, e);

throw new RuntimeException("添加水印和页码失败," + 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 + " 页 /共 " + n + " 页";

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("添加水印和页码失败," + waterMarkPath, e);

throw new RuntimeException("添加水印和页码失败," + waterMarkPath, e);

}

}

pdf加密码及权限设置

权限说明

权限

说明

ALLOW_PRINTING

文档允许打印

ALLOW_DEGRADED_PRINTING

允许用户打印文档,但不提供allow_printing质量(128位加密)

ALLOW_MODIFY_CONTENTS

允许用户修改内容,例如 更改页面内容,或插入或删除页

ALLOW_ASSEMBLY

允许用户插入、删除和旋转页面和添加书签。页面的内容不能更改,除非也授予allow_modify_contents权限,(128位加密)

ALLOW_COPY

允许用户复制或以其他方式从文档中提取文本和图形,包括使用辅助技术。例如屏幕阅读器或其他可访问设备

ALLOW_SCREENREADERS

允许用户提取文本和图形以供易访问性设备使用,(128位加密)

ALLOW_MODIFY_ANNOTATIONS

允许用户添加或修改文本注释和交互式表单字段

ALLOW_FILL_IN

允许用户填写表单字段,(128位加密)

需要多个权限时,用|拼接即可

无水印页码

在生成PDF时添加权限及密码

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转换pdf失败!", e);

throw new RuleException(ErrCodeFile.CO_HTML_TO_PDF_FAILED);

}

}

/** 用户权限,根据需求自己设置*/

private static final int PERMIT = PdfWriter.ALLOW_PRINTING;

/** pdf加密类型*/

private static final int ENCRYPTION_TYPE = PdfWriter.STANDARD_ENCRYPTION_128;

private static void setPDFEncryption(String password, String adminPassword,ITextRenderer render) {

PDFEncryption pdfEncryption = new PDFEncryption();

// 用户密码

pdfEncryption.setUserPassword(password.getBytes());

// 管理员密码

pdfEncryption.setOwnerPassword(adminPassword.getBytes());

// 用户权限

pdfEncryption.setAllowedPrivileges(PERMIT);

// 加密类型

pdfEncryption.setEncryptionType(ENCRYPTION_TYPE);

render.setPDFEncryption(pdfEncryption);

}

有水印页码

在生成PDF时无需添加权限及密码,在添加水印及页码时添加

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);

// 用户密码,管理员密码,权限,加密类型

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 + " 页 /共 " + n + " 页";

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("添加水印和页码失败," + waterMarkPath, e);

throw new RuntimeException("添加水印和页码失败," + waterMarkPath, e);

}

}

模板记录

1、字符集乱码问题

(历史原因导致开发使用GBK字符集,UTF-8的情况暂时未知)charset应与Java代码中的传入的保持一致,同时ftl文件的字符集应与此保持一致,windows使用GBK,linux使用UTF-8,否则生成PDF乱码

2、img标签

%24%7BlogoImage%7D

img标签支持base64格式data:image/png;base64,

data:,文本数据

data:text/plain,文本数据

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图片数据

data:image/png;base64,base64编码的png图片数据

data:image/jpeg;base64,base64编码的jpeg图片数据

data:image/x-icon;base64,base64编码的icon图片数据

3、强制分页

4、 head记录

xxxx

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

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值