接上一篇 SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出(二) 设置多字体, 以及中文不显示的问题
对 HtmlToPDFUtil 类进行修改升级加入封装方法
import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.annotation.Resource;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
* @Author:wangdi
* @Date:2023/6/9 14:58
* @Des: HtmlToPdfUtil 转换pdf工具类
*/
@Component
public class HtmlToPDFUtil {
@Resource
private TemplateEngine templateEngine;
/**
* 使用 Thymeleaf 渲染 HTML
*
* @param template HTML模板路径
* @param params 渲染的参数
* @return 返回渲染后的html代码
* @throws Exception
*/
public String render(String template, Map<String, Object> params) {
Context context = new Context();
if (params.size() > 0) {
context.setVariables(params);
}
//将数据填充到模板里,开始处理模板
return templateEngine.process(template, context);
}
/**
* 根据html生成pdf的base64格式
*
* @param html
* @return
*/
public static String getPDFBase64ByHtml(String html) throws IOException, DocumentException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();//构建字节输出流
ITextRenderer renderer = new ITextRenderer();
ITextFontResolver fontResolver = renderer.getFontResolver();
// html中设置的字体样式需要参考此处debug后fontResolver的fontFamily的数据的key
//指定文件字体添加到PDF库,指定字体不作为内部字体,而是外部字体被加载
fontResolver.addFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // SimSun
fontResolver.addFont("font/微软雅黑.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // Microsoft YaHei
fontResolver.addFont("font/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // SimHei
fontResolver.addFont("font/simkai.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // KaiTi
renderer.setDocumentFromString(html);
renderer.layout();
renderer.createPDF(baos);
return new BASE64Encoder().encode(baos.toByteArray());
}
/**
* 根据pdf的base64格式和路径生成pdf文件
*
* @param base64 pdf的base64格式
* @param path 生成pdf的路径
* @return
*/
public static String base64ToPDF(String base64, String path) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String fileAdd = sdf.format(new Date());
//先判断文件是否存在
path = path + "/" + fileAdd;
String fileName = path + "/" + System.currentTimeMillis() + ".pdf";//新的文件名
BufferedInputStream bin = null;
FileOutputStream fout = null;
BufferedOutputStream bout = null;
BASE64Decoder decoder = new BASE64Decoder();
try {
byte[] bytes = decoder.decodeBuffer(base64);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
// 创建从底层输入流中读取数据的缓冲输入流对象
bin = new BufferedInputStream(bais);
//获取文件夹路径
File file = new File(path);
//如果文件夹不存在则创建
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
}
// 创建到指定文件的输出流
fout = new FileOutputStream(fileName);
// 为文件输出流对接缓冲输出流对象
bout = new BufferedOutputStream(fout);
byte[] buffers = new byte[1024];
int len = bin.read(buffers);
while (len != -1) {
bout.write(buffers, 0, len);
len = bin.read(buffers);
}
// 刷新此输出流并强制写出所有缓冲的输出字节,必须这行代码,否则有可能有问题
bout.flush();
//返回存储的路径
return fileName;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bin.close();
fout.close();
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return "";
}
/**
* 合并添加水印 页眉和页脚 水印的方法 (可根据需求自行改动封装)
*
* @param pdfBase64 PDF的Base64格式
* @param isAddWatermark 是否添加水印
* @param waterMarkName 水印文字
* @param isAddPageNumbers 是否添加页眉
* @param headerText 页眉名称
* @param isAddLogoImg 是否添加Logo
* @param logoPath logo图片路径
* @param newWidth 图片大小宽度如 141
* @param newHeight 图片大小高度如30
* @param absoluteX 图片相对位置 如20
* @param absoluteY 图片相对位置 如-30
* @return
*/
public static String pdfAddWaterHeaderLogo(String pdfBase64
, boolean isAddWatermark, String waterMarkName
, boolean isAddPageNumbers, String headerText
, boolean isAddLogoImg, String logoPath, float newWidth, float newHeight, float absoluteX, float absoluteY
, boolean isAddWatermarkLogo, String watermarkLogoPath
) {
try {
byte[] bytes = new BASE64Decoder().decodeBuffer(pdfBase64);
/**
* PdfReader是iText库中的PDF解析器,用于读取和解析PDF文件。通过PdfReader对象,可以获取PDF文件的各种属性和内容,
* 如总页数、页面尺寸、文本内容等。它提供了方法来打开和关闭PDF文件,并可以从文件路径、InputStream或字节数组等多种方式加载PDF文件。
*
* PdfStamper是iText库中的PDF编辑器,用于修改和编辑PDF文件。通过PdfStamper对象,
* 可以在PDF文件中添加文本、图片、表单域等元素,修改页面内容、添加注释、加密文档等操作。它基于已解析的PdfReader对象,可以将修改后的内容写入新的PDF文件或覆盖原始文件。
*/
PdfReader reader = new PdfReader(bytes);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PdfStamper stamper = new PdfStamper(reader, bos);
int total = reader.getNumberOfPages();
if (isAddLogoImg) {
addLogoImg(stamper, reader, logoPath, newWidth, newHeight, absoluteX, absoluteY);
}
if (isAddPageNumbers) {
addPageNumbers(stamper, reader, headerText);
}
// 添加水印放到最后执行 否则会将页眉页脚进行透明度设置
if (isAddWatermark) {
addWatermark(stamper, reader, waterMarkName);
}
if (isAddWatermarkLogo) {
addWatermarkImage(stamper, reader, watermarkLogoPath);
}
stamper.close();
reader.close();
return new BASE64Encoder().encode(bos.toByteArray());
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
return null;
}
/**
* 添加水印
*
* @param stamper PdfStamper对象
* @param reader PdfReader对象
* @param waterMarkName 水印文字
* @throws DocumentException
* @throws IOException
*/
private static void addWatermark(PdfStamper stamper, PdfReader reader, String waterMarkName) throws DocumentException, IOException {
BaseFont baseFont = BaseFont.createFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
Rectangle pageRect;
PdfGState gs = new PdfGState();
gs.setFillOpacity(0.1f); // 填充的透明度
gs.setStrokeOpacity(0.1f); // 描边的透明度
JLabel label = new JLabel();
FontMetrics metrics;
int textH = 0;
int textW = 0;
label.setText(waterMarkName);
metrics = label.getFontMetrics(label.getFont());
textH = metrics.getHeight();
textW = metrics.stringWidth(label.getText());
PdfContentByte under;
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
pageRect = reader.getPageSizeWithRotation(i);
under = stamper.getOverContent(i);
under.saveState();
under.setGState(gs);
under.beginText();
under.setFontAndSize(baseFont, 20);
under.setColorFill(BaseColor.BLACK);//水印颜色
// 水印文字成30度角倾斜
for (int height = -5 + textH; height < pageRect.getHeight(); height = height + textH * 4) {
for (int width = -5 + textW; width < pageRect.getWidth() + textW; width = width + textW * 3) {
under.showTextAligned(Element.ALIGN_LEFT, waterMarkName, width - textW, height - textH, 30);
}
}
// 添加水印文字
under.endText();
}
}
/**
* 添加页眉 (页脚)
*
* @param stamper PdfStamper对象
* @param reader PdfReader对象
* @throws DocumentException
* @throws IOException
*/
private static void addPageNumbers(PdfStamper stamper, PdfReader reader, String headerText) throws DocumentException, IOException {
int total = reader.getNumberOfPages();
for (int i = 1; i <= total; i++) {
Rectangle pageRect = reader.getPageSizeWithRotation(i);
PdfContentByte content = stamper.getOverContent(i);
BaseFont baseFont = BaseFont.createFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
content.setFontAndSize(baseFont, 20);
ColumnText.showTextAligned(content, Element.ALIGN_CENTER, new Phrase(headerText, new Font(baseFont, 10)),
pageRect.getWidth() / 2, pageRect.getTop() - 20, 0);
ColumnText.showTextAligned(content, Element.ALIGN_CENTER, new Phrase("页码 " + i + " / " + total, new Font(baseFont, 10)),
(pageRect.getLeft() + pageRect.getRight()) / 2, pageRect.getBottom() + 10, 0);
}
}
/**
* 添加图片logo
*
* @param stamper PdfStamper对象
* @param reader PdfReader对象
* @param logoPath 图片路径
* @param newWidth 图片大小宽度如 141
* @param newHeight 图片大小高度如30
* @param absoluteX 图片相对位置 如20
* @param absoluteY 图片相对位置 如-30
* @throws DocumentException
* @throws IOException
*/
private static void addLogoImg(PdfStamper stamper, PdfReader reader, String logoPath, float newWidth, float newHeight, float absoluteX, float absoluteY) throws DocumentException, IOException {
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
Rectangle pageRect = reader.getPageSizeWithRotation(i);
PdfContentByte content = stamper.getOverContent(i);
BaseFont baseFont = BaseFont.createFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
content.setFontAndSize(baseFont, 20);
// 添加Logo图片到页眉
Image logoImage = Image.getInstance(logoPath);
// logoImage.scaleAbsolute(141, 30);
logoImage.scaleAbsolute(newWidth, newHeight);
logoImage.setAbsolutePosition(pageRect.getLeft() + absoluteX, pageRect.getTop() + absoluteY);
content.addImage(logoImage);
}
}
/**
* 添加水印图片
*
* @param stamper PdfStamper对象
* @param reader PdfReader对象
* @param watermarkPath 水印图片路径
* @throws DocumentException
* @throws IOException
*/
private static void addWatermarkImage(PdfStamper stamper, PdfReader reader, String watermarkPath) throws DocumentException, IOException {
Image watermarkImage = Image.getInstance(watermarkPath);
watermarkImage.scaleToFit(400, 400); // 调整水印图片大小
/**
* .scaleAbsolute .scaleToFit 这两个方法 什么区别
* scaleAbsolute和scaleToFit是iText库中用于缩放图像的两个方法。
*
* scaleAbsolute(float width, float height):
* 该方法用于将图像缩放到指定的绝对宽度和高度。
* 参数width和height分别指定了缩放后的宽度和高度。
* 图像将按照指定的宽度和高度进行缩放,可能会导致图像的比例失调。
*
* scaleToFit(float width, float height):
* 该方法用于将图像缩放以适应指定的矩形框的大小。
* 参数width和height指定了矩形框的宽度和高度。
* 图像将按照比例缩放,以适应指定的矩形框,同时保持其宽高比。
* 简而言之,scaleAbsolute方法按照指定的宽度和高度进行缩放,而scaleToFit方法会按照比例缩放以适应指定的矩形框大小。选择使用哪个方法取决于你的需求和图像的要求。
*/
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
Rectangle pageRect = reader.getPageSizeWithRotation(i);
PdfContentByte content = stamper.getOverContent(i);
// 创建PdfGState对象并设置透明度
PdfGState gState = new PdfGState();
gState.setFillOpacity(0.1f); // 水印图片透明度 (0.0f - 1.0f,0.0f 表示完全透明,1.0f 表示完全不透明)
content.saveState();
content.setGState(gState);
// 添加水印图片到页面
float x = (pageRect.getLeft() + pageRect.getRight() - watermarkImage.getScaledWidth()) / 2;
float y = (pageRect.getBottom() + pageRect.getTop() - watermarkImage.getScaledHeight()) / 2;
content.addImage(watermarkImage, watermarkImage.getScaledWidth(), 0, 0, watermarkImage.getScaledHeight(), x, y);
content.restoreState();
}
}
}
使用
String base64 = HtmlToPDFUtil.getPDFBase64ByHtml(html);
String base64O = HtmlToPDFUtil.pdfAddWaterHeaderLogo(base64
, true, "水印测试123"
, true, "页眉测试123"
, true, pathResolver.getResourcePath("classpath:static/img/logo-0acbf217.png"), 141, 30, 20, -30
, true, pathResolver.getResourcePath("classpath:static/img/waterLogo_b.png")
);
String pdfNumberF = HtmlToPDFUtil.base64ToPDF(base64O, "D:\\______________________________________IDEA\\__SpringBoot\\spring20_pdf\\src\\main\\resources");
System.out.println(pdfNumberF);
接下一篇 SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出(四) 表格中断问题