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> |
复制