基于SpringBoot使用Thymeleaf+iText实现html(带图片)转pdf文件

1.导入依赖

<!-- Thymeleaf 模板引擎 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency><groupId>org.xhtmlrenderer</groupId><artifactId>flying-saucer-pdf</artifactId><version>9.1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ognl/ognl 这个依赖不引入会抛异常-->
<dependency><groupId>ognl</groupId><artifactId>ognl</artifactId><version>3.1.12</version>
</dependency>
<!--io常用工具类 -->
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.7.1</version>
</dependency>

2.创建thylemeaf模板

根据模板生成文件,可以在模板里指定格式,在 resources/templtes目录下创建一个模板

htmlTemplate.html文件就是我的模板,font/SIMSUN.TTC文件解决转pdf是解决中文的字体,后面需要引入。

编写模板htmlTemplate.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><head><style type="text/css">body {font-family: SimSun;}.content_wrap {padding: 20px 45px;background: white;-webkit-print-color-adjust: exact;border-radius: 4px;}._header {position: relative;font-size: 18px;line-height: 1;color: black;}._header::after {position: absolute;left: -15px;top: -10%;transform: translateY(-50%);content: "";display: block;width: 5px;height: 20px;background-color: #00bba6;-webkit-print-color-adjust: exact;}.infos {margin: 10px 0 30px 0;padding: 20px 0px;}.infos span {width: 33%;margin-top: 20px;margin-right: 10px;}._info-label {font-size: 14px;font-weight: 400;color: #8E97A5;line-height: 14px;margin-right: 10px;}._info-value {font-size: 14px;font-weight: 400;color: #2A3245;line-height: 14px;}._table {margin-top: 20px;border: 1px solid #E1E6EC;border-collapse: collapse;}._table tr {border-bottom: 1px solid #E1E6EC;height: 48px;}._table ._tr-val {height: 60px;}td {border-top: 0;border-right: 1px #E1E6EC solid;border-bottom: 1px #E1E6EC solid;border-left: 0;text-align: right;}table {border-top: 1px #E1E6EC solid;border-right: 0;border-bottom: 0;border-left: 1px #E1E6EC solid;}._table td {text-align: right;padding-right: 10px;font-weight: bold;font-size: 14px;color: #2A3245;border-right: 1px solid #E1E6EC;}._table .t-tithle {background: #F3F6F9;-webkit-print-color-adjust: exact;}._foot p {margin-top: 20px;}._foot ._label {font-size: 14px;font-weight: 400;color: #5F677A;line-height: 14px;margin-right: 45px;}._foot ._value {font-size: 20px;font-weight: bold;color: #2A3245;line-height: 20px;}</style></head><body><!-- 使用 utext 可以识别html标签--><div class="detail-content content_wrap pl30" th:utext="${content}"></div></body>
</html>

3.有图片,需要把图片转成 iText 的图片对象,需要转成Base64编码,用到如下类

package com.cqbay.maserb.factory;import com.lowagie.text.BadElementException;
import com.lowagie.text.Image;
import com.lowagie.text.pdf.codec.Base64;
import org.w3c.dom.Element;
import org.xhtmlrenderer.extend.FSImage;
import org.xhtmlrenderer.extend.ReplacedElement;
import org.xhtmlrenderer.extend.ReplacedElementFactory;
import org.xhtmlrenderer.extend.UserAgentCallback;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.pdf.ITextFSImage;
import org.xhtmlrenderer.pdf.ITextImageElement;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;import java.io.IOException;/*** @ClassName: Base64ImgReplacedElementFactory* @Description: TODO* @Author: Jane* @Date: 2020/7/8 14:07* @Version: V1.0**/
public class Base64ImgReplacedElementFactory implements ReplacedElementFactory {/***  * 实现createReplacedElement 替换html中的Img标签*  **  * @param c 上下文*  * @param box 盒子*  * @param uac 回调*  * @param cssWidth css宽*  * @param cssHeight css高*  * @return ReplacedElement**/@Overridepublic ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac, int cssWidth, int cssHeight) {Element e = box.getElement();if (e == null) {return null;}String nodeName = e.getNodeName();// 找到img标签if (nodeName.equals("img")) {String attribute = e.getAttribute("src");FSImage fsImage;try {// 生成itext图像fsImage = buildImage(attribute, uac);} catch (BadElementException e1) {fsImage = null;} catch (IOException e1) {fsImage = null;}if (fsImage != null) {// 对图像进行缩放if (cssWidth != -1 || cssHeight != -1) {fsImage.scale(cssWidth, cssHeight);}return new ITextImageElement(fsImage);}}return null;}/***  * 编解码base64并生成itext图像**/protected FSImage buildImage(String srcAttr, UserAgentCallback uac) throws IOException,BadElementException {FSImage fiImg = null;//图片的src要为src="https://img-blog.csdnimg.cn/2022010616555048137.jpg"这种base64格式if (srcAttr.toLowerCase().startsWith("data:image/")) {String base64Code = srcAttr.substring(srcAttr.indexOf("base64,") + "base64,".length(), srcAttr.length());// 解码byte[] decodedBytes = Base64.decode(base64Code);fiImg = new ITextFSImage(Image.getInstance(decodedBytes));} else {fiImg = uac.getImageResource(srcAttr).getImage();}return fiImg;}@Overridepublic void reset() {}@Overridepublic void remove(Element arg0) {}@Overridepublic void setFormSubmissionListener(FormSubmissionListener arg0) {}
}

4.生成PDF的工具类

PdfUtils

package com.cqbay.maserb;import com.lowagie.text.DocumentException;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import com.cqbay.maserb.factory.Base64ImgReplacedElementFactory
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @ClassName: PdfUtils* @Description: pdf工具类* @Author: Jane* @Date: 2020/7/8 14:27**/
@Slf4j
public class PdfUtils {/*** 字体路径*/public static String FONT_PATH = "font/SIMSUN.TTC";/*** html模板路径*/public static final String HTML_TEMPLATE_PATH = "templates/pdf/htmlTemplate.html";/*** 生成的pdf保存目录,这里是固定了目录,文件名需自己拼装*/public static final String PDF_BASE_PATH = "src/main/resources/pdf/";/*** PDF扩展名*/public static final String PDF_EXTENSION = ".pdf";/*** 图片基础URL*/public static final String IMG_SAVE_URL = "http://117.78.37.58:8989/files/";/*** 图片保存路径*/public static final String IMG_SAVE_PATH = "src/main/resources/img/";public static TemplateEngine templateEngine = new TemplateEngine();/*** 读取文件** @param file* @return 读取文件的内容*/public static String getFileString(File file) {log.info("开始读取文件");BufferedReader reader = null;StringBuffer sb = new StringBuffer();try {reader = new BufferedReader(new FileReader(file));String tempStr;while ((tempStr = reader.readLine()) != null) {sb.append(tempStr);}reader.close();log.info("读取文件完成,文件信息:file={}", sb.toString());return sb.toString();} catch (IOException e) {log.error("文件读取失败,cause--->" + e.getMessage());} finally {if (reader != null) {try {reader.close();} catch (IOException e1) {e1.printStackTrace();}}}return sb.toString();}/*** 根据模板生成html文件** @param htmlTemplatePath html模板* @param content          填充内容* @return                  根据模板生成的html* @throws IOException*/public static String getHtml(String htmlTemplatePath, String content) {if (StringUtils.isEmpty(htmlTemplatePath)) {htmlTemplatePath = HTML_TEMPLATE_PATH;}if (StringUtils.isEmpty(content)) {log.debug("生成html失败:内容content为未空");return null;}try {log.info("开始是生成html文件");Resource resource = new ClassPathResource(htmlTemplatePath);File sourceFile = resource.getFile();Context context = new Context();// 将内容写入模板Map<String, Object> params = new HashMap<>();params.put("content", content);context.setVariables(params);return templateEngine.process(getFileString(sourceFile), context);} catch (IOException e) {log.error("html文件生成失败:cause--->" + e.getMessage());return null;}}/*** 根据html生成PDF** @param html html内容* @param file 输出pdf文件的路径* @throws DocumentException* @throws IOException*/public static void htmlToPdf(String html, File file) {/*** 切记 css 要定义在head 里,否则解析失败* css 要定义字体* 例如宋体style="font-family:SimSun"用simsun.ttc*/if (!file.exists()) {try {if (file.getParentFile() != null && !file.getParentFile().exists()) {file.getParentFile().mkdirs();}file.createNewFile();} catch (IOException e) {e.printStackTrace();}}log.info("开始根据html生成pdf,html={}", html);OutputStream out = null;try {out = new FileOutputStream(file);ITextRenderer renderer = new ITextRenderer();// 携带图片,将图片标签转换为itext自己的图片对象renderer.getSharedContext().setReplacedElementFactory(new Base64ImgReplacedElementFactory());renderer.getSharedContext().getTextRenderer().setSmoothingThreshold(0);// 解决中文支持问题ITextFontResolver fontResolver = renderer.getFontResolver();// 字体名称要大写,否则可能找不到fontResolver.addFont(FONT_PATH, "Identity-H", false);renderer.setDocumentFromString(html);// 如果是本地图片使用 file:,这里指定图片的父级目录。html上写相对路径,// renderer.getSharedContext().setBaseURL("file:/E:/img/")// 处理图片renderer.getSharedContext().setBaseURL(IMG_SAVE_URL);renderer.layout();renderer.createPDF(out);out.flush();log.info("pdf生成成功");} catch (DocumentException e) {log.error("pdf生成失败,cause--->" + e.getMessage());} catch (IOException e) {log.error("pdf生成失败,cause--->" + e.getMessage());} finally {try {if (null != out) {out.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 替换http图片url为其相对url* 例如:http://117.78.37.58:8989/files/20200327\png\dcb09254b0d049d28550a9f31d8e88af.png* 替换成:20200327/png/dcb09254b0d049d28550a9f31d8e88af.png* 注意:一定要把 url中的所有 "\" 替换成 "/" 否者图片可能不会显示,原因不明** @param html* @return html字符串*/public static String replaceImgTagSrc(String html) {log.info("开始替换图片");if (StringUtils.isEmpty(html)) {log.debug("图片替换入参html为空");return null;}// 解析htmlDocument document = Jsoup.parse(html);Elements imgList = document.getElementsByTag("img");if (ObjectUtils.isEmpty(imgList) || imgList.size() == 0) {log.debug("html中没有图片需要替换");return html;}List<String> srcList = new ArrayList();for (Element img : imgList) {// 获取src的值String src = img.attr("src");srcList.add(src);}log.info("html中img标签src值列表srcList={}", srcList);
//         遍历下载图片
//        List<String> imgPathList = downloadImg(srcList);// 遍历获取图片相对路径List<String> subImgUrlList = new ArrayList();for (String imgUrl : srcList) {// 我这里是用的http图片,所有图片都放在 files 下的,所以从 files/ 后面开始截取// 获取图片相对路径,并把路径中的 "\" 替换成 "/"String subImgUrl = imgUrl.substring(imgUrl.indexOf("files") + "files".length() + 1).replaceAll("\\\\", "/");subImgUrlList.add(subImgUrl);}log.info("图片子路径列表subImgUrlList={}", subImgUrlList);// 替换for (int i = 0; i < imgList.size(); i++) {imgList.get(i).attr("src", subImgUrlList.get(i));}log.info("图片替换完成后的html={}", document.toString());return document.toString();}/*** 批量下载图片** @param imgUrlList 图片链接* @return List<String> 本地存储路径列表*/public static List<String> downloadImg(List<String> imgUrlList) {try {if (ObjectUtils.isEmpty(imgUrlList) || imgUrlList.size() == 0) {log.info("图片路径列表入参不能为空");return null;}List<String> imgPathList = new ArrayList();for (String imgUrl : imgUrlList) {if (!"".equals(imgUrl)) {String replaceImgUrl = "";if (imgUrl.contains("\\")) {replaceImgUrl = imgUrl.replaceAll("\\\\", "/");} else {replaceImgUrl = imgUrl;}String fileName = replaceImgUrl.substring(replaceImgUrl.lastIndexOf("/") + 1);String localImgPath = System.getProperty("user.dir") + "/" + IMG_SAVE_PATH + fileName;// 下载URL url = new URL(imgUrl);URLConnection connection = url.openConnection();InputStream is = connection.getInputStream();byte[] bs = new byte[1024];int len;File file = new File(localImgPath);// 图片不存在,下载图片if (!file.exists()) {try {if (file.getParentFile() != null && !file.getParentFile().exists()) {file.getParentFile().mkdirs();}file.createNewFile();} catch (IOException e) {e.printStackTrace();}FileOutputStream os = new FileOutputStream(file, true);while ((len = is.read(bs)) != -1) {os.write(bs, 0, len);os.flush();}os.close();is.close();imgPathList.add(localImgPath);} else {// 图片存在,直接使用imgPathList.add(localImgPath);}} else {imgPathList.add(imgUrl);}}return imgPathList;} catch (IOException e) {e.printStackTrace();return null;}}
}

5.运行测试

package com.cqbay.maserb;import java.io.File;/*** @ClassName: TestMain* @Description: TODO* @Author: Jane* @Date: 2020/7/8 15:22* @Version: V1.0**/
public class PdfTest {// html片段 private static final String content = "<p style=\"text-align: justify;\"><img class=\"wscnph\" src=\"http://117.78.37.58:8989/files/20200327\\png\\dcb09254b0d049d28550a9f31d8e88af.png\" /></p>\n" +"<p style=\"text-align: left;\">犀牛是国bai家稀有动物之一,也是du国家级保护动物。</p>\n" +"<p style=\"text-align: left;\">&nbsp;</p>\n" +"<p style=\"text-align: left;\">犀牛身体庞大bai,四肢粗du壮,体重一般都在三千斤左右。它的皮又厚又硬,足以挡住任何动物的袭击。犀牛鼻子上张着一只或两只坚硬的角,在动物王国里抵抗力和杀伤力都是数一数二的。任何猛兽连人都难以打倒它,它发起怒来连附近的树木植物都难逃厄运,就连狮子、老虎等大型陆地动物在犀牛发怒时都得逃之夭夭。</p>\n" +"<p style=\"text-align: left;\">&nbsp;</p>\n" +"<p style=\"text-align: left;\">犀牛的皮肤虽然厚糙,可皮肤中的细缝却柔嫩,成为了寄生虫、蚊子等吸血昆虫的青睐。可它有一位如影随形的好朋友——犀牛鸟,它以犀牛皮肤空隙里的吸血虫为食。这样既帮助犀牛除出祸害,又让自己饱餐一顿,可真是一举两得啊!</p>\n" +"<p style=\"text-align: left;\">&nbsp;</p>\n" +"<p style=\"text-align: left;\">犀牛拥有高度近视,它的好朋友犀牛鸟却视力良好。在发现情敌的时候,犀牛鸟就会“叽叽喳喳”向犀牛提醒。这时,犀牛就会迅速逃离现场,让敌人枉费心机。</p>\n" +"<p style=\"text-align: left;\">&nbsp;</p>\n" +"<p style=\"text-align: left;\">虽然犀牛以稀有而收到世人的保护,可仍有一些不法之徒向犀牛伸出魔爪,让我们一起来保护犀牛,保护野生动物吧!!!</p>\n" +"<p style=\"text-align: left;\">&nbsp;</p>";public static void main(String[] args) {// 把html片段中的图片url替换成相对url,采用Jsoup解析String replaceImgTagSrc = PdfUtils.replaceImgTagSrc(content);//把替换后的html片段根据Thymeleaf模板生成htmlString html = PdfUtils.getHtml(PdfUtils.HTML_TEMPLATE_PATH, replaceImgTagSrc);// 把html转成pdf,这里将生成的pdf文件放在E盘下PdfUtils.htmlToPdf(html, new File("E:/pdfTest.pdf"));}}

6.测试结果

参考:<https://www.cnblogs.com/yunfeiyang-88/p/10984740.html

Java使用Thylemeaf + iText实现html(带图片)转pdf文件相关推荐

  1. 将HTML文件转换为PDF文件(Thymeleaf模板转换,简单解决中文问题, 解决HTML带图片转换PDF文件问题)

    将HTML文件转换为PDF文件 一.导入依赖 <!-- itext生成Pdf --> <dependency><groupId>com.itextpdf</g ...

  2. Java 使用iText7生成带页码的PDF文件(同时生成目录,但是不会合并两个PDF)

    一.效果图 1.带页码效果 2.目录效果 前言:Java 使用iText7生成带页码的PDF文件,同时生成目录PDF,但限于水平,暂时还在摸索合并两个PDF.不过看了一下,iText好像有生成目录的代 ...

  3. 【Java编程系列】java用POI、Itext生成并下载PPT、PDF文件

    热门系列: [Java编程系列]WebService的使用 [Java编程系列]在Spring MVC中使用工具类调用Service层时,Service类为null如何解决 [Java编程系列]Spr ...

  4. 详解如何创建带图片的pdf

    随时随地技术实战干货,获取项目源码.学习资料,请关注源代码社区公众号(ydmsq666) 在上一篇文章itextpdf基本使用中介绍了itextpdf开源库创建pdf的步骤和创建基本文本pdf的方法以 ...

  5. java图片转换pdf_Java实现图片转换PDF文件的示例代码

    最近因为一些事情,需要将一张简单的图片转换为PDF的文件格式,在网上找了一些工具,但是这些工具不是需要注册账号,就是需要下载软件. 而对于只是转换一张图片的情况下,这些操作显然是非常繁琐的,所以作者就 ...

  6. 用VSCode打开带图片的.md文件

    最近自学python发现一个特别好的教程,里面文档都是.md文件并且带图片的,我用sublime,UE等都能打开 不能显示图片,所以就找到用VSCode打开. 把大象放进冰箱分三步,打开带图片的.md ...

  7. Java实现图片转换PDF文件

    文章目录 引入依赖 前端页面 控制层接口 PDF工具类 页面效果 最近因为一些事情,需要将一张简单的图片转换为PDF的文件格式,在网上找了一些工具,但是这些工具不是需要注册账号,就是需要下载软件. 而 ...

  8. java实现图片转pdf文件

    2021年新年快乐!在此祝大家代码无bug~~~ 由于我是驻场开发,前段时间问我要个证件扫描件的PDF文件,我一想,现在转换pdf文件是要花钱的啊,这是我能忍受的了的吗!!! 答案当然是不能啊,我的贫 ...

  9. android pdfjet_GitHub - lnj721/PdfBuilder: Android端使用图片生成PDF文件

    PdfBuilder Android端使用图片生成PDF文件 一.应用场景 从本地选择图片生成pdf文件,由于Android本身并没有对pdf的支持,这里选择使用一个第三方的库来达成需求. 二.库的选 ...

  10. Android 如何加载网页、图片以及PDF文件之项目实战

    这里对于App有需求需要做webView加载页面以及图片,pdf文件等,可以参考一下这篇文章: 我在做项目应用时,有个需求是把用户不同类型的数据上传到后台. 对于用户的数据大体分为以下类型: 图片(j ...

最新文章

  1. java emailbuilder 样式_Java8通用Builder了解一下
  2. 通过蜜罐技术获取攻击者手机号、微信号【网络安全】
  3. 重磅!神策客户服务中心升级,5 年,价值释放加速度
  4. P1989 无向图三元环计数 思维 + 建图
  5. 做老板必须要有正气和底气,所谓正气就是身正不怕影子斜
  6. scholarscope不显示影响因子_一劳永逸:这两个查看影响因子的插件你安装了吗?...
  7. go语言实战学习笔记
  8. MATLAB中text函数使用
  9. Channel 用法
  10. MySQL 检索昵称字段合法中文/日文字符对于 5.7/5.6 版本失败的解决方案
  11. 计算机桌面属性打不开,电脑计算机属性打不开怎么办
  12. Python-Level1-day16:异常处理try-exceptraise语句,for迭代原理,深入手写创建迭代器;yield浅出使用生成器
  13. 一篇文章带你快速上手Airtest和Poco
  14. 百度智能云身份证识别API的使用
  15. Genessential获近千万元天使轮融资,明年推出AGEs 手持检测设备...
  16. 2022年全国技能大赛云计算 RocketChat聊天系统上云
  17. 如何在Eclipse中显示空格(space)和制表符(tab)
  18. Sublime text3配置切换大小写转换
  19. max、opengl和d3d使用的坐标系
  20. SSH工具客户端软件大全

热门文章

  1. OpenCV学习笔记-Shi-Tomasi角点检测
  2. Load和Initialize的区别和使用
  3. 金山词霸2010牛津旗舰破解版【最完美的】的使用方案
  4. C#爬取数据_详细篇
  5. oracle 复杂判断,单条SQL语句实现复杂逻辑几例~~
  6. Ubuntu下快捷方式图标存放位置
  7. 我们一起学程序-五指棋
  8. 未转变者服务器简单,未转变者服务器搭建教程详细讲解
  9. 大数据和人工智能属于什么专业 - 学大数据和人工智能出来做什么
  10. 火星探险问题 网络流