最近项目中需要生成日报文件,日报文件的格式为pdf,且日报的样式相对而言比较复杂,存在多段文字,存在多个表格,且存在样式。目前想到的解决办法是
先生成html文件,让后将html文件转换成pdf文件。通过网上搜索,发现openhtmltopdf可以实现我们的需求,此处记录一下。
1、html的生成,我们可以通过freemarker来实现。
2、html转pdf,通过openhtmltopdf来实现。
首先搭建一个简单的可运行的程序,可实现Freemarker渲染模板,然后生成pdf文件
org.springframework.boot
spring-boot-starter-web
2.6.0
org.freemarker
freemarker
2.3.30
com.openhtmltopdf
openhtmltopdf-pdfbox
1.0.10
org.projectlombok
lombok
1.18.36
加载程序中src/main/resources/templates/ftls目录下的模板文件,然后渲染成html内容。
package com.huan.pdf.utils;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.StringWriter;
import java.util.Map;
/**
* freemarker 工具类
*
* @author admin
*/
@Slf4j
public class FreemarkerUtils {
/**
* 模板文件夹路径
*/
private static final String TEMPLATE_DIR = "/templates/ftls";
private static final Configuration CONFIGURATION;
static {
CONFIGURATION = new Configuration(Configuration.VERSION_2_3_30);
CONFIGURATION.setTemplateLoader(new ClassTemplateLoader(FreemarkerUtils.class, TEMPLATE_DIR));
CONFIGURATION.setDefaultEncoding("UTF-8");
CONFIGURATION.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
CONFIGURATION.setLogTemplateExceptions(false);
CONFIGURATION.setWrapUncheckedExceptions(true);
}
/**
* 根据模板名称和数据模型生成字符串
*
* @param templateName 模板名称
* @param dataModel 数据模型
* @return 生成的字符串
*/
public static String processTemplate(String templateName, Map dataModel) {
try {
Template template = CONFIGURATION.getTemplate(templateName);
StringWriter writer = new StringWriter();
template.process(dataModel, writer);
return writer.toString();
} catch (Exception e) {
log.error("解析模板出现问题", e);
}
return "";
}
}
编写pdf工具类,用于将html内容渲染成pdf文件,此处只是简单实现,后期该类还需要修改
package com.huan.pdf.utils;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
/**
* pdf工具类
*
* @author admin
*/
@Slf4j
public class PdfUtils {
/**
* 生成pdf文件
*
* @param pdfTemplate pdf模板
* @param response http response
*/
public static void generatePdf(String pdfTemplate, HttpServletResponse response) {
// 设置响应头
String fileName = UUID.randomUUID() + ".pdf";
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
try (OutputStream os = response.getOutputStream()) {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.withHtmlContent(pdfTemplate, null);
builder.toStream(os);
builder.run();
} catch (IOException e) {
log.error("生成pdf文件失败", e);
throw new RuntimeException("生成pdf文件失败", e);
}
}
}
生成pdf
${mainTitle}
该模板中存在变量mainTitle,这个变量的值通过后台来赋值
package com.huan.pdf.controller;
import com.huan.pdf.utils.FreemarkerUtils;
import com.huan.pdf.utils.PdfUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* pdf控制器
*
* @author admin
*/
@RestController
public class PdfController {
@GetMapping("pdf")
public void pdf(HttpServletResponse response) {
Map params = new HashMap(16);
params.put("mainTitle", "这是一个标题 - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 渲染模板
String htmlContent = FreemarkerUtils.processTemplate("pdf.ftl", params);
// 生成pdf
PdfUtils.generatePdf(htmlContent, response);
}
}
注意:此处的mainTitle中存在中文,生产的Pdf会乱码待会儿在处理
可以看到可以正常的生成pdf了,但是中文乱码了。 至此我们一个简单的程序就搭建完成了,下面让我们来完善功能。
默认情况下生成的pdf,中文是乱码的,若需要解决这个问题,就需要引入中文字体。此处我们使用宋体。
在程序的src/main/resources/fonts目录下,引入宋体(simsun.ttf)
builder.useFont(() -> PdfUtils.class.getClassLoader().getResourceAsStream("fonts/simsun.ttf"), "SimSun");
从上图中可以看到,现在已经可以展示中文了。
此处实现将生成的pdf中的 这是一个标题-时间 这句话的字体修改成红色。
.main-title { text-align: center; font-size:25px; color:#FF0000; }
通过上图可知,样式已经生效了。
序号
1
2
3
4
5
从上图可以看到,生成的pdf,内容跨了2页,那么如何解决这个问题呢?通过css样式解决
table { border-collapse: collapse; page-break-inside: auto;}
tr { page-break-inside: avoid;}
通过css样式page-break-before:always开启新的一页pdf。
默认情况是A4 纵向,现在我想修改成A3 横向。这个指定对所有的页面都生效,不可只对某一个页面生效,若想对某一个页面生效,可以生成多个pdf文件,然后进行pdf文件的合并操作。
@page{ size:A3 landscape; }
从上图中可知 正好是A3横向
实现思路:通过pdfbox生成加密的密码,此处给默认密码a0nin13s
/**
* 生成带密码的 PDF 文件(用户密码 a0min13s)
*
* @param pdfTemplate HTML 模板字符串
* @param response HTTP 响应
*/
public static void generatePdf(String pdfTemplate, HttpServletResponse response) {
String fileName = UUID.randomUUID() + ".pdf";
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// 1. 先用 openhtmltopdf 生成未加密 PDF(内存)
ByteArrayOutputStream temp = new ByteArrayOutputStream();
try {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useFont(() -> PdfUtils.class.getClassLoader().getResourceAsStream("fonts/simsun.ttf"), "SimSun");
builder.withHtmlContent(pdfTemplate, null);
builder.toStream(temp);
// 完成渲染
builder.run();
} catch (IOException e) {
log.error("生成PDF失败", e);
throw new RuntimeException("生成PDF失败");
}
// 用 PDFBox 加载并加密
try (PDDocument doc = PDDocument.load(temp.toByteArray());
OutputStream os = response.getOutputStream()) {
AccessPermission ap = new AccessPermission();
// 可选:禁止打印、复制等
ap.setCanPrint(false);
ap.setCanExtractContent(false);
// 用户密码,所有者密码一样即可(也可设不同)
StandardProtectionPolicy policy =
// ownerPwd userPwd
new StandardProtectionPolicy("a0min13s", "a0min13s", ap);
// 128 位 AES
policy.setEncryptionKeyLength(128);
policy.setPermissions(ap);
// 执行加密
doc.protect(policy);
// 写给浏览器
doc.save(os);
// 确保全部送出
os.flush();
} catch (IOException e) {
log.error("PDF加密输出失败", e);
throw new RuntimeException("PDF加密输出失败");
}
}
https://gitee.com/huan1993/spring-cloud-parent/tree/master/pdf/openhtmltopdf
本文来自博客园,作者:huan1993,转载请注明原文链接:https://www.cnblogs.com/huan1993/p/19113277
登录查看全部
参与评论
手机查看
返回顶部