集成freemarker+flying-saucer-pdf+itext,通过html模板生成PDF

折腾了很久,flying-saucer-pdf终于完美解决了(中文问题,换行问题,页眉页脚,水印),html+css控制pdf样式

一共集成到两个类中:Generator & PDFBuilder,具体看详细的代码注释,相关文件路径自行修改

转黄方法入口:Generator.pdfGeneratePlus

1.引入相关java

org.xhtmlrenderer

flying-saucer-pdf-itext5

9.1.18

com.itextpdf

itext-asian

5.2.0

com.itextpdf.tool

xmlworker

5.5.11

org.freemarker

freemarker

2.3.28

2.上上面提到两个类的实现代码

package com.xxxxx.xxxx.file.config;

import java.io.BufferedWriter;

import java.io.ByteArrayInputStream;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.StringWriter;

import java.nio.charset.Charset;

import java.util.Map;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import org.springframework.util.StringUtils;

import org.xhtmlrenderer.pdf.ITextRenderer;

import com.itextpdf.text.Document;

import com.itextpdf.text.Font;

import com.itextpdf.text.Image;

import com.itextpdf.text.Rectangle;

import com.itextpdf.text.pdf.BaseFont;

import com.itextpdf.text.pdf.PdfWriter;

import com.itextpdf.tool.xml.Pipeline;

import com.itextpdf.tool.xml.XMLWorker;

import com.itextpdf.tool.xml.XMLWorkerFontProvider;

import com.itextpdf.tool.xml.XMLWorkerHelper;

import com.itextpdf.tool.xml.html.CssAppliersImpl;

import com.itextpdf.tool.xml.html.Tags;

import com.itextpdf.tool.xml.net.FileRetrieve;

import com.itextpdf.tool.xml.net.ReadingProcessor;

import com.itextpdf.tool.xml.parser.XMLParser;

import com.itextpdf.tool.xml.pipeline.css.CSSResolver;

import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline;

import com.itextpdf.tool.xml.pipeline.end.PdfWriterPipeline;

import com.itextpdf.tool.xml.pipeline.html.AbstractImageProvider;

import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline;

import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;

import com.itextpdf.tool.xml.pipeline.html.ImageProvider;

import freemarker.template.Configuration;

import freemarker.template.Template;

import lombok.extern.slf4j.Slf4j;

/**

*

* @描述:html/pdf生成器

*

* @作者:zhongjy

*

* @时间:2019年7月15日 下午12:31:25

*/

@Slf4j

public class Generator {

/**

*

* @描述:生成html

*

* @返回:String

*

* @作者:zhongjy

*

* @时间:2019年7月15日 下午12:33:58

*/

public static String htmlGenerate(String template, Map variables)

throws Exception {

Configuration config = FreemarkerConfiguration.getConfiguation();

Template tp = config.getTemplate(template);

StringWriter stringWriter = new StringWriter();

BufferedWriter writer = new BufferedWriter(stringWriter);

tp.process(variables, writer);

String htmlStr = stringWriter.toString();

writer.flush();

writer.close();

return htmlStr;

}

public static void pdfGenerate(String htmlStr, OutputStream out) throws Exception {

DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

org.w3c.dom.Document doc = builder.parse(new ByteArrayInputStream(htmlStr.getBytes()));

ITextRenderer renderer = new ITextRenderer();

renderer.setDocument(doc, null);

renderer.layout();

renderer.createPDF(out);

out.close();

}

/**

*

* @描述:生成pdf

*

* @返回:void

*

* @作者:zhongjy

*

* @时间:2019年7月16日 上午10:59:11

*/

public static void pdfGeneratePlus(String htmlTemplate, Map dataMap,

String targetPdf, Rectangle pageSize, String header, boolean isFooter, File watermark)

throws Exception {

/**

* 根据freemarker模板生成html

*/

String htmlStr = htmlGenerate(htmlTemplate, dataMap);

final String charsetName = "UTF-8";

Document document = new Document(pageSize);

OutputStream out = new FileOutputStream(targetPdf);

/**

* 设置边距

*/

// document.setMargins(30, 30, 30, 30);

PdfWriter writer = PdfWriter.getInstance(document, out);

/**

* 添加页码

*/

PDFBuilder builder = new PDFBuilder(header, 10, pageSize, watermark, isFooter);

writer.setPageEvent(builder);

document.open();

/**

* html内容解析

*/

HtmlPipelineContext htmlContext =

new HtmlPipelineContext(new CssAppliersImpl(new XMLWorkerFontProvider() {

@Override

public Font getFont(String fontname, String encoding, float size, final int style) {

if (fontname == null) {

/**

* 操作系统需要有该字体, 没有则需要安装; 当然也可以将字体放到项目中, 再从项目中读取

*/

fontname = "STSong-Light";

encoding = "UniGB-UCS2-H";

}

Font font = null;

try {

font = new Font(BaseFont.createFont(fontname, encoding, BaseFont.NOT_EMBEDDED), size,

style);

} catch (Exception e) {

log.error("", e);

}

return font;

}

})) {

@Override

public HtmlPipelineContext clone() throws CloneNotSupportedException {

HtmlPipelineContext context = super.clone();

ImageProvider imageProvider = this.getImageProvider();

context.setImageProvider(imageProvider);

return context;

}

};

/**

* 图片解析

*/

htmlContext.setImageProvider(new AbstractImageProvider() {

String rootPath = "C:\\Users\\Administrator\\Desktop\\刘亦菲\\";

@Override

public String getImageRootPath() {

return rootPath;

}

@Override

public Image retrieve(String src) {

if (StringUtils.isEmpty(src)) {

return null;

}

try {

Image image = Image.getInstance(new File(rootPath, src).toURI().toString());

/**

* 图片显示位置

*/

image.setAbsolutePosition(400, 400);

if (image != null) {

store(src, image);

return image;

}

} catch (Exception e) {

log.error("", e);

}

return super.retrieve(src);

}

});

htmlContext.setAcceptUnknown(true).autoBookmark(true)

.setTagFactory(Tags.getHtmlTagProcessorFactory());

/**

* css解析

*/

CSSResolver cssResolver = XMLWorkerHelper.getInstance().getDefaultCssResolver(true);

cssResolver.setFileRetrieve(new FileRetrieve() {

@Override

public void processFromStream(InputStream in, ReadingProcessor processor) throws IOException {

try (InputStreamReader reader = new InputStreamReader(in, charsetName)) {

int i = -1;

while (-1 != (i = reader.read())) {

processor.process(i);

}

} catch (Throwable e) {

}

}

/**

* 解析href

*/

@Override

public void processFromHref(String href, ReadingProcessor processor) throws IOException {

InputStream is = new ByteArrayInputStream(href.getBytes());

try {

InputStreamReader reader = new InputStreamReader(is, charsetName);

int i = -1;

while (-1 != (i = reader.read())) {

processor.process(i);

}

} catch (Exception e) {

log.error("", e);

}

}

});

HtmlPipeline htmlPipeline =

new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer));

Pipeline> pipeline = new CssResolverPipeline(cssResolver, htmlPipeline);

XMLWorker worker = null;

worker = new XMLWorker(pipeline, true);

XMLParser parser = new XMLParser(true, worker, Charset.forName(charsetName));

try (InputStream inputStream = new ByteArrayInputStream(htmlStr.getBytes())) {

parser.parse(inputStream, Charset.forName(charsetName));

}

document.close();

}

}

package com.xxxxx.xxxx.file.config;

import java.io.File;

import com.itextpdf.text.BaseColor;

import com.itextpdf.text.Document;

import com.itextpdf.text.Element;

import com.itextpdf.text.Font;

import com.itextpdf.text.Image;

import com.itextpdf.text.PageSize;

import com.itextpdf.text.Phrase;

import com.itextpdf.text.Rectangle;

import com.itextpdf.text.pdf.BaseFont;

import com.itextpdf.text.pdf.ColumnText;

import com.itextpdf.text.pdf.PdfContentByte;

import com.itextpdf.text.pdf.PdfPageEventHelper;

import com.itextpdf.text.pdf.PdfTemplate;

import com.itextpdf.text.pdf.PdfWriter;

import com.xxxxx.xxxx.util.BaseUtil;

import lombok.extern.slf4j.Slf4j;

/**

*

* @描述:PDF生成水印和页眉页脚(页码).

*

* @作者:zhongjy

*

* @时间:2019年7月15日 下午8:22:24

*/

@Slf4j

public class PDFBuilder extends PdfPageEventHelper {

/**

* 页眉

*/

public String header = "";

/**

* 文档字体大小,页脚页眉最好和文本大小一致

*/

public int presentFontSize = 10;

/**

* 文档页面大小,最好前面传入,否则默认为A4纸张

*/

public Rectangle pageSize = PageSize.A4;

/**

* 模板

*/

public PdfTemplate total;

/**

* 基础字体对象

*/

public BaseFont bf = null;

/**

* 利用基础字体生成的字体对象,一般用于生成中文文字

*/

public Font fontDetail = null;

/**

* 水印文件

*/

private File watermark = null;

/**

* 是否显示页脚页码信息

*/

private boolean isHeaderFooter = false;

public PDFBuilder() {

}

/**

*

* @param header

* @param presentFontSize

* @param pageSize

* @param watermark

*/

public PDFBuilder(String header, int presentFontSize, Rectangle pageSize, File watermark,

boolean isHeaderFooter) {

this.header = header;

this.presentFontSize = presentFontSize;

this.pageSize = pageSize;

this.watermark = watermark;

this.isHeaderFooter = isHeaderFooter;

}

public void setHeader(String header) {

this.header = header;

}

public void setPresentFontSize(int presentFontSize) {

this.presentFontSize = presentFontSize;

}

/**

* 文档打开时创建模板

*/

public void onOpenDocument(PdfWriter writer, Document document) {

/**

* 共 页 的矩形的长宽高

*/

total = writer.getDirectContent().createTemplate(50, 50);

}

/**

* 关闭每页的时候添加页眉页脚和水印

*/

public void onEndPage(PdfWriter writer, Document document) {

/**

* 添加分页

*/

if (isHeaderFooter) {

this.addPage(writer, document);

}

/**

* 添加水印

*/

if (watermark != null) {

this.addWatermark(writer);

}

}

/**

*

* @描述:分页

*

* @返回:void

*

* @作者:zhongjy

*

* @时间:2019年7月15日 下午9:09:39

*/

public void addPage(PdfWriter writer, Document document) {

try {

if (bf == null) {

bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", false);

}

if (fontDetail == null) {

/**

* 数据体字体

*/

fontDetail = new Font(bf, presentFontSize, Font.NORMAL);

fontDetail.setColor(BaseColor.GRAY);

}

} catch (Exception e) {

log.error("", e);

}

/**

* 写入页眉

*/

ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT,

new Phrase(header, fontDetail), document.left(), document.top() + 20, 0);

/**

* 写入页脚(分页信息)

*/

int pageS = writer.getPageNumber();

String foot1 = "第 " + pageS + " 页 / 共";

Phrase footer = new Phrase(foot1, fontDetail);

/**

* 计算前半部分的foot1的长度,后面好定位最后一部分的'Y页'这俩字的x轴坐标,字体长度也要计算进去 = len

*/

float len = bf.getWidthPoint(foot1, presentFontSize);

/**

* 拿到当前的PdfContentByte

*/

PdfContentByte cb = writer.getDirectContent();

/**

* 写入页脚1,x轴就是(右margin+左margin + right() -left()- len)/2.0F 再给偏移20F适合人类视觉感受,否则肉眼看上去就太偏左了

* y轴就是底边界-20,否则就贴边重叠到数据体里了就不是页脚了;注意Y轴是从下往上累加的,最上方的Top值是大于Bottom好几百开外的

*/

ColumnText.showTextAligned(cb, Element.ALIGN_CENTER, footer,

(document.rightMargin() + document.right() + document.leftMargin() - document.left() - len)

/ 2.0F + 20F,

document.bottom() - 25, 0);

/**

* 写入页脚2的模板(就是页脚的Y页这俩字)添加到文档中,计算模板的和Y轴,X=(右边界-左边界 - 前半部分的len值)/2.0F + len , y 轴和之前的保持一致,底边界-20

*/

cb.addTemplate(total,

(document.rightMargin() + document.right() + document.leftMargin() - document.left()) / 2.0F

+ 20F,

document.bottom() - 25);

}

/**

*

* @描述:添加水印

*

* @返回:void

*

* @作者:zhongjy

*

* @时间:2019年7月15日 下午9:12:40

*/

public void addWatermark(PdfWriter writer) {

/**

* 水印图片

*/

Image image = null;

try {

image = Image.getInstance(BaseUtil.file2byte(watermark));

PdfContentByte content = writer.getDirectContentUnder();

content.beginText();

/**

* 开始写入水印

*/

image.setAbsolutePosition(300, 300);

content.addImage(image);

content.endText();

} catch (Exception e) {

log.error("", e);

}

}

/**

* 关闭文档时,替换模板,完成整个页眉页脚组件

*/

public void onCloseDocument(PdfWriter writer, Document document) {

if (isHeaderFooter) {

/**

* 最后一步了,就是关闭文档的时候,将模板替换成实际的 Y 值,至此,page x of y 制作完毕,完美兼容各种文档size

*/

total.beginText();

/**

* 生成的模版的字体、颜色

*/

total.setFontAndSize(bf, presentFontSize);

total.setColorFill(BaseColor.GRAY);

String foot2 = " " + (writer.getPageNumber()) + " 页";

/**

* 模版显示的内容

*/

total.showText(foot2);

total.endText();

total.closePath();

}

}

}

最后附上HTML模板和调用方法

table {

width: 100%;

border-collapse: collapse;

border-style: solid;

border-width: 0.5px;

border-color: #000000;

}

table tr td {

border-width: 0.5px;

border-style: solid;

border-color: #000000;

padding: 9px 9px;

}

table thead tr th {

border-width: 0.5px;

border-style: solid;

border-color: #000000;

text-align: center;

padding: 9px 9px;

}

${title}

#list>

${d}#list>

#list>

import java.util.ArrayList;

import java.util.Arrays;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import com.itextpdf.text.PageSize;

import com.xxxxx.xxxx.file.config.Generator;

public class Test4 {

public static void main(String[] args) throws Exception {

Map mp = new HashMap();

try {

String outputFile = "C:\\Users\\Administrator\\Desktop\\test123\\abc1.pdf";// 生成后的路径

Map dataMap = new HashMap();

List titleList = Arrays.asList("属性1", "属性2", "属性3", "属性4", "属性5", "属性6", "属性7");

dataMap.put("titleList", titleList);

List> dataList = new ArrayList>();

for (int i = 0; i < 100; i++) {

dataList

.add(Arrays.asList("数据1_" + i, "数据2_" + i, "数据3_数据3_数据3_数据3_数据3_数据3_数据3_数据3_数据3_" + i,

"数据4_" + i, "数据5_" + i, "数据6_" + i, "数据7_" + i));

}

dataMap.put("dataList", dataList);

//File water = new File("C:\\Users\\zhongjy\\Desktop\\test123\\water.png");

Generator.pdfGeneratePlus("laytable/normal-teble.html", dataMap, outputFile, PageSize.A4, "", true, null);

mp.put("code", "200");

mp.put("url", outputFile);

} catch (Exception ex) {

ex.printStackTrace();

mp.put("code", "500");

}

}

}

运行结果图如下:

补充上面用到的方法

/**

*

* @描述:文件转byte[]

*

* @返回:byte[]

*

* @作者:zhongjy

*

* @时间:2019年7月15日 下午10:19:18

*/

public static byte[] file2byte(File file) {

FileInputStream fileInputStream = null;

byte[] bFile = null;

try {

bFile = new byte[(int) file.length()];

fileInputStream = new FileInputStream(file);

fileInputStream.read(bFile);

} catch (Exception e) {

logger.error("", e);

} finally {

if (fileInputStream != null) {

try {

fileInputStream.close();

} catch (Exception e) {

logger.error("", e);

}

}

}

return bFile;

}

/**

*

* @描述:html报表模板配置

*

* @作者:zhongjy

*

* @时间:2019年7月15日 下午12:25:59

*/

public class FreemarkerConfiguration {

private static Configuration config = null;

static {

config = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);

config.setClassForTemplateLoading(FreemarkerConfiguration.class, "/report/");

}

public static Configuration getConfiguation() {

return config;

}

}

说明:/report/是springboot项目下freemarker的模板路径

————————————————

版权声明:本文为CSDN博主「zhong_jianyu」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zhong_jianyu/java/article/details/96147949

html pdf支持css%写法吗,flying-saucer-pdf终于完美解决了(中文问题,换行问题,分页,页眉页脚,水印),html+css控制pdf样式...相关推荐

  1. SpringBoot html转pdf 支持中文、图片水印+文字水印、页眉页脚 flying-saucer-pdf-itext5 + freemarker

    使用 flying-saucer-pdf-itext5加freemarker生成pdf,支持中文.图片水印+文字水印.页眉页脚. 引入jar包 <!-- freemarker --> &l ...

  2. freemarker转PDF,支持分页,增加页眉页脚

    参考 https://github.com/superad/pdf-kit.git 先看效果(不能上传PDF文档...) POM.XML <?xml version="1.0" ...

  3. 前端导出多页pdf 带目录 页眉 页脚及页码

    前段时间公司发布新需求,要求用户点击按钮可以导出pdf或者html到本地,pdf中要包含可点击跳转的目录,要分页记录页码,还有页眉和页脚,和后台的小哥哥配合试了好多方法,最终完成的效果还不错,在这里做 ...

  4. web端生成pdf,前端生成pdf导出并自定义页眉页脚

    web前端生成pdf文档 描述 解决办法 技术栈 逻辑 直接上代码,后边再唠叨,注释写的还算清晰吧 用到的方法 模拟数据 最终版截图扔这儿一个 开始唠叨 需求 梳理 决定 缺点 描述 前端导出pdf文 ...

  5. java pdf 页眉_itext生成PDF设置页眉页脚的实例详解

    itext生成PDF设置页眉页脚的实例详解 实例代码: /** * ITextTest * iText生成PDF加入列表,注释等内容,同时设置页眉和页脚及页码等. */ package com.lab ...

  6. itextpdf7 使用之 html 转 pdf 页眉页脚带图片

    之前使用 itextpdf5 html 转 pdf,发现有些 css 样式在转换后会缺失,现在升级一下版本,itextpdf7 升级之后,改动挺大的,基本上重构了,但确实好使了 安装 官方文档: ht ...

  7. itext总页数_itext 生成pdf文件添加页眉页脚

    原文来自:https://www.cnblogs.com/joann/p/5511905.html 我只是记录所有jar版本,由于版本冲突及不兼容很让人头疼的,一共需要5个jar, 其中itextpd ...

  8. c# .net生成pdf创建pdf,pdf签名pdf合并pdf增删页面页眉页脚批注旋转提取图片文本加水印等的类库SharpPDF

    SharpPDF是一款在.net平台实现PDF生成和编辑的解决方案级产品.可以在Winform,WPF,WebAPI,WebService,MVC,WebForm等多种类型项目中,轻松实现一行代码生成 ...

  9. itext对已经存在的pdf添加页眉页脚

    接上一篇拼接pdf后,需要对不同文件的pdf展示不同的页眉及页脚,所以,这篇分享对于已存在的pdf进行页眉页脚的添加. public static String RederAndCopyByPDF(S ...

最新文章

  1. python基础知识练习题
  2. 解答:为什么蚊子咬的包会痒痒
  3. 保持你的决心——《传说之下》背后的设计之道
  4. 一步步学习微软InfoPath2010和SP2010--第八章节--使用InfoPath表单Web部件
  5. linux下简单的邮件配置
  6. Julia:调用python函数的几种方法
  7. 模块ntdll中出现异常eaccessviolation_SAP ERP软件中的物料凭证 MIGO
  8. 目标跟踪算法MOSSE笔记
  9. 软件需求说明书怎么写
  10. FPGA课程:JESD204B的应用场景(干货分享)
  11. DUXCMS 2.x学习问题(一)
  12. Java扫码点餐小程序源码 SaaS系统源码 微信、支付宝扫码点餐小程序源代码
  13. python打印输出数组中的所有元素
  14. 关于OSGI中的Felix热插拔技术
  15. 【DRF+Django】微信小程序入门到实战_day04(上)
  16. STM32固件库(标准外设库)入门学习 第四章OLED屏幕使用
  17. 涨姿势了,蜻蜓FM源码剖析
  18. 解决Warning: Leaking Caffe2 thread-pool after fork
  19. ByteBuf 读取字节数组数据
  20. 咕咕机_GT1,能放到口袋里的迷你打印机

热门文章

  1. PLC供电系统的保护措施
  2. LeetCode简单题643.子数组的最大平均数I
  3. CodeForces - 1413C Perform Easily(双指针)
  4. python 操作鼠标和键盘
  5. SpringBoot集成文件 - 如何基于POI-tl和word模板导出庞大的Word文件?
  6. android获取指纹信息最新,# android 指纹识别并检测指纹库是否变更
  7. android 微信评论功能,Android仿微信朋友圈点击评论自动定位到相关行功能
  8. 中国农业会计杂志中国农业会计杂志社中国农业会计编辑部2022年第12期目录
  9. 易桌面打印室一般多久能到,易桌面打印室怎么用
  10. 淘宝女装店铺如何提升转化?