基于Java編寫(xiě)一個(gè)html轉(zhuǎn)pdf的工具類
1、背景
最近項(xiàng)目中需要生成日?qǐng)?bào)文件,日?qǐng)?bào)文件的格式為pdf,且日?qǐng)?bào)的樣式相對(duì)而言比較復(fù)雜,存在多段文字,存在多個(gè)表格,且存在樣式。目前想到的解決辦法是 先生成html文件,讓后將html文件轉(zhuǎn)換成pdf文件。通過(guò)網(wǎng)上搜索,發(fā)現(xiàn)openhtmltopdf可以實(shí)現(xiàn)我們的需求,此處記錄一下。
2、需求
生成的pdf需要支持中文。
生成的pdf支持簡(jiǎn)單的樣式。(此處可以使用css樣式來(lái)解決,但不是所有的css樣式都支持)
生成的pdf存在表格,每行應(yīng)完整地出現(xiàn)在同一頁(yè),不要一半在上一頁(yè)、一半在下一頁(yè)。
生成的pdf可以自己指定到分頁(yè),比如某個(gè)表格的數(shù)據(jù)渲染完之后,需要單獨(dú)開(kāi)啟一頁(yè)。
生成的pdf支持密碼加密。
生成的pdf可以支持紙張規(guī)格,比如是A3還是A4,并且還可設(shè)置橫向還是縱向。
3、思路
1、html的生成,我們可以通過(guò)freemarker來(lái)實(shí)現(xiàn)。
2、html轉(zhuǎn)pdf,通過(guò)openhtmltopdf來(lái)實(shí)現(xiàn)。
4、實(shí)現(xiàn)步驟
4.1 搭建一個(gè)簡(jiǎn)單的工程
首先搭建一個(gè)簡(jiǎn)單的可運(yùn)行的程序,可實(shí)現(xiàn)Freemarker渲染模板,然后生成pdf文件
引入依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.0</version>
</dependency>
<!-- 模板引擎,用于渲染html -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<!-- 用于將html轉(zhuǎn)換成pdf -->
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<version>1.0.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</dependency>
</dependencies>
編寫(xiě)Freemarker工具類
加載程序中src/main/resources/templates/ftls目錄下的模板文件,然后渲染成html內(nèi)容。
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);
}
/**
* 根據(jù)模板名稱和數(shù)據(jù)模型生成字符串
*
* @param templateName 模板名稱
* @param dataModel 數(shù)據(jù)模型
* @return 生成的字符串
*/
public static String processTemplate(String templateName, Map<String, Object> dataModel) {
try {
Template template = CONFIGURATION.getTemplate(templateName);
StringWriter writer = new StringWriter();
template.process(dataModel, writer);
return writer.toString();
} catch (Exception e) {
log.error("解析模板出現(xiàn)問(wèn)題", e);
}
return "";
}
}
編寫(xiě)pdf工具類
編寫(xiě)pdf工具類,用于將html內(nèi)容渲染成pdf文件,此處只是簡(jiǎn)單實(shí)現(xiàn),后期該類還需要修改
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) {
// 設(shè)置響應(yīng)頭
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);
}
}
}
增加一個(gè)模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>生成pdf</title>
<style>
.main-title { text-align: center; font-size:25px; }
</style>
</head>
<body>
<div class="main-title">${mainTitle}</div>
</body>
</html>
該模板中存在變量mainTitle,這個(gè)變量的值通過(guò)后臺(tái)來(lái)賦值
增加一個(gè)控制層
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<String, Object> params = new HashMap<>(16);
params.put("mainTitle", "這是一個(gè)標(biāo)題 - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 渲染模板
String htmlContent = FreemarkerUtils.processTemplate("pdf.ftl", params);
// 生成pdf
PdfUtils.generatePdf(htmlContent, response);
}
}
注意:此處的mainTitle中存在中文,生產(chǎn)的Pdf會(huì)亂碼待會(huì)兒在處理
運(yùn)行

可以看到可以正常的生成pdf了,但是中文亂碼了。 至此我們一個(gè)簡(jiǎn)單的程序就搭建完成了,下面讓我們來(lái)完善功能。
4.2 功能完善
生成的pdf需要支持中文
默認(rèn)情況下生成的pdf,中文是亂碼的,若需要解決這個(gè)問(wèn)題,就需要引入中文字體。此處我們使用宋體。
1.程序中引入宋體
在程序的src/main/resources/fonts目錄下,引入宋體(simsun.ttf)

2.pdf工具類中增加使用中文字體
builder.useFont(() -> PdfUtils.class.getClassLoader().getResourceAsStream("fonts/simsun.ttf"), "SimSun");

3.freemarker模板中使用中文字體
<style>
body { font-family: "SimSun"; font-size: 16px; line-height: 1.5; color: #000;}
</style>

4.運(yùn)行

從上圖中可以看到,現(xiàn)在已經(jīng)可以展示中文了。
生成的pdf支持簡(jiǎn)單的樣式
此處實(shí)現(xiàn)將生成的pdf中的 這是一個(gè)標(biāo)題-時(shí)間 這句話的字體修改成紅色。
1.freemarker模板中使用css樣式
.main-title { text-align: center; font-size:25px; color:#FF0000; }

2.運(yùn)行

通過(guò)上圖可知,樣式已經(jīng)生效了。
表格的某一行不要出現(xiàn)跨頁(yè)
1.freemarker模板中增加一個(gè)表格
<style>
table { border-collapse: collapse; }
td { border: 1px solid black; padding: 70px;}
</style>
<table>
<tr><td>序號(hào)</td></tr>
<tr><td>1</td></tr>
<tr><td>2</td></tr>
<tr><td>3</td></tr>
<tr><td>4</td></tr>
<tr><td>5</td></tr>
</table>

2.查看效果

從上圖可以看到,生成的pdf,內(nèi)容跨了2頁(yè),那么如何解決這個(gè)問(wèn)題呢?通過(guò)css樣式解決
3.css解決
table { border-collapse: collapse; page-break-inside: auto;}
tr { page-break-inside: avoid;}
4.查看效果

單獨(dú)開(kāi)啟一頁(yè)pdf
1.freemarker模板修改
通過(guò)css樣式page-break-before:always開(kāi)啟新的一頁(yè)pdf。

2.查看效果

指定pdf頁(yè)面的規(guī)格
默認(rèn)情況是A4 縱向,現(xiàn)在我想修改成A3 橫向。這個(gè)指定對(duì)所有的頁(yè)面都生效,不可只對(duì)某一個(gè)頁(yè)面生效,若想對(duì)某一個(gè)頁(yè)面生效,可以生成多個(gè)pdf文件,然后進(jìn)行pdf文件的合并操作。
css樣式指定頁(yè)面規(guī)則
@page{ size:A3 landscape; }
查看效果

從上圖中可知 正好是A3橫向
pdf 加密
實(shí)現(xiàn)思路:通過(guò)pdfbox生成加密的密碼,此處給默認(rèn)密碼a0nin13s
1.修改pdf生成的工具類
/**
* 生成帶密碼的 PDF 文件(用戶密碼 a0min13s)
*
* @param pdfTemplate HTML 模板字符串
* @param response HTTP 響應(yīng)
*/
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(內(nèi)存)
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();
// 可選:禁止打印、復(fù)制等
ap.setCanPrint(false);
ap.setCanExtractContent(false);
// 用戶密碼,所有者密碼一樣即可(也可設(shè)不同)
StandardProtectionPolicy policy =
// ownerPwd userPwd
new StandardProtectionPolicy("a0min13s", "a0min13s", ap);
// 128 位 AES
policy.setEncryptionKeyLength(128);
policy.setPermissions(ap);
// 執(zhí)行加密
doc.protect(policy);
// 寫(xiě)給瀏覽器
doc.save(os);
// 確保全部送出
os.flush();
} catch (IOException e) {
log.error("PDF加密輸出失敗", e);
throw new RuntimeException("PDF加密輸出失敗");
}
}
2.查看效果

完整代碼:https://gitee.com/huan1993/spring-cloud-parent/tree/master/pdf/openhtmltopdf
以上就是基于Java編寫(xiě)一個(gè)html轉(zhuǎn)pdf的工具類的詳細(xì)內(nèi)容,更多關(guān)于Java html轉(zhuǎn)pdf的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java實(shí)現(xiàn)將PDF轉(zhuǎn)換為HTML的高效解決方案與實(shí)踐
- Java實(shí)現(xiàn)word,pdf轉(zhuǎn)html并保留格式
- java實(shí)現(xiàn)html轉(zhuǎn)pdf方法步驟
- Java中將Html轉(zhuǎn)換為PDF的方法和步驟
- Java?將PDF轉(zhuǎn)為HTML時(shí)保存到流的方法和步驟
- JAVA實(shí)現(xiàn)PDF轉(zhuǎn)HTML文檔的示例代碼
- Java實(shí)現(xiàn)Word/Pdf/TXT轉(zhuǎn)html的實(shí)例代碼
- Java實(shí)現(xiàn)Html轉(zhuǎn)Pdf的方法
相關(guān)文章
spring.factories文件的解析源碼API機(jī)制詳解
通過(guò)本文深入探討Spring?Boot的背景歷史、業(yè)務(wù)場(chǎng)景、功能點(diǎn)以及底層原理,使讀者對(duì)Spring?Boot有了更深入的了解,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-11-11
Java調(diào)用第三方接口示范的實(shí)現(xiàn)
這篇文章主要介紹了Java調(diào)用第三方接口示范的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
Java?jar打包成exe應(yīng)用程序的詳細(xì)步驟
本文主要介紹了Java?jar打包成exe應(yīng)用程序的詳細(xì)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
JavaWeb使用mvc模式實(shí)現(xiàn)登錄功能
本文主要介紹了JavaWeb使用mvc模式實(shí)現(xiàn)登錄功能,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
EasyExcel工具讀取Excel空數(shù)據(jù)行問(wèn)題的解決辦法
EasyExcel是阿里巴巴開(kāi)源的一個(gè)excel處理框架,以使用簡(jiǎn)單,節(jié)省內(nèi)存著稱,下面這篇文章主要給大家介紹了關(guān)于EasyExcel工具讀取Excel空數(shù)據(jù)行問(wèn)題的解決辦法,需要的朋友可以參考下2022-08-08
Log4j2?重大漏洞編譯好的log4j-2.15.0.jar包下載(替換過(guò)程)
Apache?開(kāi)源項(xiàng)目?Log4j?的遠(yuǎn)程代碼執(zhí)行漏洞細(xì)節(jié)被公開(kāi),由于?Log4j?的廣泛使用,該漏洞一旦被攻擊者利用會(huì)造成嚴(yán)重危害,下面小編給大家?guī)?lái)了Log4j2?重大漏洞編譯好的log4j-2.15.0.jar包下載,感興趣的朋友一起看看吧2021-12-12
詳解如何使用tldb數(shù)據(jù)庫(kù)的java客戶端
這篇文章主要為大家介紹了如何使用tldb數(shù)據(jù)庫(kù)的java客戶端過(guò)程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Java實(shí)現(xiàn)的簡(jiǎn)單字符串反轉(zhuǎn)操作示例
這篇文章主要介紹了Java實(shí)現(xiàn)的簡(jiǎn)單字符串反轉(zhuǎn)操作,結(jié)合實(shí)例形式分別描述了java遍歷逆序輸出以及使用StringBuffer類的reverse()方法兩種字符串反轉(zhuǎn)操作技巧,需要的朋友可以參考下2018-08-08
SpringMVC xml文件路徑在web.xml中的配置方式
這篇文章主要介紹了SpringMVC xml文件路徑在web.xml中的配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

