1、依赖 gradle
implementation 'com.itextpdf:html2pdf:5.0.0'
2、JAVA代码
import cn.hutool.core.io.FileUtil;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
import com.itextpdf.layout.font.FontProvider;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;
@Slf4j
public class PdfGenerator {
private static final Configuration CFG;
static {
CFG = new Configuration(new Version("2.3.30"));
CFG.setDefaultEncoding("UTF-8");
CFG.setClassForTemplateLoading(PdfGenerator.class, "/pdf/template");
}
public static byte[] getPdfByte(Map<String, Object> dataModel, String templateName) throws Exception {
try {
Template template = CFG.getTemplate(templateName + ".ftl");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(outputStream);
PdfDocument pdf = new PdfDocument(writer);
pdf.addEventHandler(PdfDocumentEvent.START_PAGE, new Watermark("aaaa"));
StringWriter htmlWriter = new StringWriter();
template.process(dataModel, htmlWriter);
String html = htmlWriter.toString();
ConverterProperties converterProperties = new ConverterProperties();
FontProvider fontProvider = new FontProvider();
fontProvider.addFont(PdfFontFactory.createFont("/pdf/font/msyh.ttc,1", "Identity-H").getFontProgram(), "Identity-H");
converterProperties.setFontProvider(fontProvider);
HtmlConverter.convertToPdf(html, pdf, converterProperties);
return outputStream.toByteArray();
} catch (Exception e) {
log.error("PDF 生成失败:", e);
throw e;
}
}
static class Watermark implements IEventHandler {
private final String text;
public Watermark(String text) {
this.text = text;
}
@Override
public void handleEvent(Event event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfPage page = docEvent.getPage();
PdfDocument document = docEvent.getDocument();
PdfCanvas canvas = new PdfCanvas(page.newContentStreamBefore(), page.getResources(), document);
try {
PdfExtGState extGState = new PdfExtGState();
extGState.setFillOpacity(0.05f);
double alpha = Math.toRadians(15);
double tanAlpha = Math.tan(alpha);
for (int i = 0; i < 10; i++) {
canvas.saveState()
.concatMatrix(1, tanAlpha, 0, 1, 0, 0)
.beginText()
.resetFillColorGray()
.setExtGState(extGState)
.moveText(200, 400 + i * 20)
.setFontAndSize(PdfFontFactory.createFont("/pdf/font/msyh.ttc,1", "Identity-H"), 10)
.showText(text)
.endText()
.restoreState();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws SystemException {
byte[] pdfByte = getPdfByte(null, "ReceiptTemplate");
FileUtil.writeBytes(pdfByte, new File("out.pdf"));
}
}
ftl 模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
@page {
@bottom-center {
content: counter(page) "/" counter(pages);
font-size: 10px;
}
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-size: 10px;
color: #333;
}
.inner-Wrap {
width: 535px;
margin: 0 auto;
}
.title {
padding: 30px 0 13px;
text-align: center;
font-size: 17px;
}
.line {
width: 100%;
height: 1px;
background-color: #000;
}
.info-box {
padding: 20px 0 10px;
}
.info-tag-flex1 {
display: flex;
}
.info-tag-flex1-child1 {
width: 300px;
}
.info-tag-flex1-child2 {
}
.info-tag-flex2 {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.info-box .info-tag-box {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
.info-box .info-tag-box .info-tag {
min-width: 122px;
}
.info-box .info-tag-box .info-tag-1 {
width: 75px!important;
}
.table {
width: 100%;
border-collapse: collapse;
border: 1px solid #000;
}
.table tr {
page-break-inside: avoid;
page-break-after: auto;
}
.table tr td {
border: 1px solid #000;
text-align: center;
}
.table .td-title {
height: 30px;
font-size: 15px;
}
.table .td1 {
height: 30px;
width: 30px;
}
.table .td2 {
height: 30px;
width: 92px;
}
.table .td3 {
height: 30px;
width: 185px;
}
.table .td4 {
height: 30px;
width: 30px;
}
.table .td5 {
height: 30px;
}
.table .td6 {
height: 30px;
}
.goods-td {
height: 25px;
}
.total {
height: 30px;
}
.table-remark-box {
min-height: 100px;
text-align: left;
padding: 10px;
}
.table-remark-tag {
}
.table-remark-sign {
margin-top: 20px;
margin-left: 206px;
}
.table-remark-desc{
line-height: 19px;
}
.table-remark-warn{
line-height: 19px;
}
tr, td, th, tbody, thead, tfoot {
page-break-inside: avoid;
}
</style>
</head>
<body>
<div class="inner-Wrap">
<div class="title">222222</div>
<div class="line"></div>
<div class="info-box">
<div class="info-tag-flex1">
<div class="info-tag-box info-tag-flex1-child1">
<div class="info-tag">33333:</div>
<div class="info-desc">${orderId}</div>
</div>
</div>
<table class="table">
<thead>
<tr>
<td colspan="6" class="td-title">333333</td>
</tr>
<tr>
<td class="td1">111</td>
<td class="td2">222</td>
<td class="td3">333</td>
<td class="td4">444</td>
<td class="td5">555</td>
<td class="td6">P66</td>
</tr>
</thead>
<tbody>
<#if skuList?has_content>
<#list skuList as sku>
<tr>
<td class="goods-td">${sku_index + 1}</td>
<td class="goods-td">${sku.skuNo!"-"}</td>
<td class="goods-td">${sku.skuName!"-"}</td>
<td class="goods-td">${sku.skuCount!"-"}</td>
<td class="goods-td">${sku.use!"-"}</td>
<td class="goods-td">${sku.poNo!"-"}</td>
</tr>
</#list>
</#if>
<tr>
<td colspan="3" class="total">汇总</td>
<td>${skuListTotal}</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</body>