Java 实现Freemarker动态HTML转PDF 内带条形码
maven jar 准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>html2pdf</artifactId> <version>2.1.7</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency> <dependency> <groupId>net.sf.barcode4j</groupId> <artifactId>barcode4j-light</artifactId> <version>2.0</version> <exclusions> <exclusion> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> </exclusion> </exclusions> </dependency> |
准备好字体文件ttf,要不会出现中文乱码,最好放在resource里面

由于我的project是关于香港的业务,要用到繁体字。
PMingLiU-CN.ttf 是新细明体
MSYHBD.TTF 是微软雅黑,因为标题要加粗,所有要加入一些粗体,不然CSS的font-weight: 600;不能起到加粗的效果。
html 转换pdf PDFUtils.java
由于我的项目是关于信封的内容,因此我本身不知道html显示的内容的高度进行分页,而且业务里要求需要页数和总页数,页脚。所有我只能在生成pdf之后再用itext加入PDF文件的页数及其他。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.itextpdf.html2pdf.ConverterProperties; import com.itextpdf.html2pdf.HtmlConverter; import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider; import com.itextpdf.styledxmlparser.css.media.MediaDeviceDescription; import com.itextpdf.styledxmlparser.css.media.MediaType; import hk.com.hkbn.itbss.sys.service.helper.SftpHelper; import hk.com.hkbn.ordletter.bo.GenPdfParam; import hk.com.hkbn.ordletter.exception.OrdLetterException; /** * HTML to PDF * */ public class PDFUtils { private static final Logger logger = LoggerFactory.getLogger(PDFUtils.class); public static void htmlFileToPDFStream(GenPdfParam param) throws OrdLetterException { try { if (param.getHtmlFile() == null) { throw new OrdLetterException("htmlFile is null"); } if (param.getPdfPath() == null) { throw new OrdLetterException("output path is null"); } // import font ttf resource ConverterProperties converter = new ConverterProperties(); DefaultFontProvider defaultFontProvider = new DefaultFontProvider(false, false, false); defaultFontProvider.addFont(param.getFontBasePath()+"/PMingLiU-CN.ttf"); defaultFontProvider.addFont(param.getFontBasePath()+"/MSYHBD.ttf"); converter.setFontProvider(defaultFontProvider); converter.setCharset("UTF-8"); // 设置pdf的显示方式,用screen代替print MediaDeviceDescription mediaDeviceDescription = new MediaDeviceDescription(MediaType.PRINT); converter.setMediaDeviceDescription(mediaDeviceDescription); //make html to pdf HtmlConverter.convertToPdf(new FileInputStream(param.getHtmlFile()), new FileOutputStream(param.getPdfPath()), converter); logger.info("make pdf finish"); //add barcode page number AddBarCodePageNum.addBarCodePageNum(param); // uploadOrdLetter(param.getNewpdfPath(),"kim_uploap_test.pdf"); logger.info("upload sftp success"); } catch (Exception e) { e.printStackTrace(); throw new OrdLetterException("create html to pdf error"); } } public static void uploadOrdLetter(String filepath ,String filename) { try { InputStream inputStream = new FileInputStream(filepath); SftpHelper sftpHelper = new SftpHelper(); sftpHelper.uploadFileToOutputPath(inputStream, filename); } catch (Exception e) { e.printStackTrace(); } } } |
加入页码和页脚以及pdf中的条形码AddBarCodePageNum.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.itextpdf.text.BadElementException; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; 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.pdf.BaseFont; import com.itextpdf.text.pdf.ColumnText; import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.text.pdf.PdfImportedPage; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfWriter; import com.itextpdf.text.pdf.parser.PdfReaderContentParser; import hk.com.hkbn.ordletter.bo.GenPdfParam; import hk.com.hkbn.ordletter.entity.LetterObj; import hk.com.hkbn.ordletter.util.Assert; /** * @description PDF add bar code \ page number \ page footer */ public class AddBarCodePageNum { /** * add bar code and page footer * * @param orgPdfPath * @param outputPdfPath * @param barcodepath * @return */ private static final Logger logger = LoggerFactory.getLogger(AddBarCodePageNum.class); public static void addBarCodePageNum(GenPdfParam param) { try (// ouput file io FileOutputStream fos = new FileOutputStream(param.getNewpdfPath());) { // make new file , default size A4 Document document = new Document(PageSize.A4); PdfWriter writer = PdfWriter.getInstance(document,fos); document.open(); // PDF content PdfContentByte pdfContent = writer.getDirectContent(); // input pdf file content io steam PdfReader reader = new PdfReader(param.getPdfPath()); // Get resource file total pages int num = reader.getNumberOfPages(); List<LetterObj> nextLetterList = new ArrayList<LetterObj>(); // keyword for (int page = 1; page <= num; page++) { LetterObj letterObj = matchPage(reader, page, "@next_Letter_Flag@"); if (Assert.isNotNull(letterObj)&&letterObj.isNextLetterNumber()) { letterObj.setPageNumber(page); nextLetterList.add(letterObj); } } logger.info("total page " + num); if (num <= 0 || nextLetterList.size() <= 0) { logger.info("no page data"); return; } Integer grobalInteger = 1; Integer prePosition = 0; for (LetterObj letterObj : nextLetterList) { Integer preNumber = 0; if (prePosition > 0) { preNumber = nextLetterList.get(prePosition - 1).getPageNumber(); } String barcodevalue = param.getBarCodeValueMap().get(prePosition); for (int i = 1; i <= letterObj.getPageNumber() - preNumber; i++) { document.newPage(); // set empty footer writer.setPageEmpty(false); // add page num and page footer onEndPage(writer, document, i, letterObj.getPageNumber() - preNumber, letterObj.getLetterLangFlag(),barcodevalue,prePosition,param); PdfImportedPage page = writer.getImportedPage(reader, grobalInteger); pdfContent.addTemplate(page, 0, 0); if (grobalInteger < nextLetterList.get(nextLetterList.size() - 1).getPageNumber()) grobalInteger++; } prePosition++; } /* for (int i = 1; i <= num; i++) { document.newPage(); // set empty writer.setPageEmpty(false); //add page num and page footer onEndPage(writer, document,i,num,language,fontBasePath,barcodepath); PdfImportedPage page =writer.getImportedPage(reader, i); pdfContent.addTemplate(page, 0, 0); } */ document.close(); reader.close(); writer.close(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } /** * Find next letter pageNumber by keyword * * @param reader * @param pageNumber * @param keyword * @return * @throws Exception */ public static LetterObj matchPage(PdfReader reader, Integer pageNumber, String keyword) throws Exception { PdfReaderContentParser parse = new PdfReaderContentParser(reader); // listen KeyWordListener renderListener = new KeyWordListener(); renderListener.setKeyword(keyword); renderListener.setPageNumber(pageNumber); parse.processContent(pageNumber, renderListener); return renderListener.getListenObj(); } /** * make left bar code * * @param document * @param BarcodePath * @throws DocumentException */ public static void addBarcode(Document document, String BarcodePath) throws DocumentException { try { Image image = Image.getInstance(BarcodePath); image.setRotationDegrees(90);// image 90 image.setAbsolutePosition(10, 70); // image.scaleAbsolute(200,30); // setting image size image.scaleToFit(30,300); document.add(image); } catch (BadElementException | IOException e) { e.printStackTrace(); } } /** * make first page bar code * * @param document * @param BarcodePath * @throws DocumentException */ public static void addFirstPageBarcode(Document document, String BarcodePath) throws DocumentException { try { Image image = Image.getInstance(BarcodePath); image.setAbsolutePosition(52, 595); // setting image size image.scaleAbsolute(250,30); // image.scaleToFit(200,300); document.add(image); } catch (BadElementException | IOException e) { e.printStackTrace(); } } /** * make page number * * @param writer * @param document * @param maxPage */ public static void onEndPage(PdfWriter writer, Document document, Integer currentPage, Integer maxPage, String language, String bracodevalue,Integer LetterRecordCount,GenPdfParam param) { try { // PDF content PdfContentByte pdfContent = writer.getDirectContent(); pdfContent.saveState(); pdfContent.beginText(); int footerFontSize = 8; String footerNum = null; String fontBasePath = null; if ("C".equals(language) || language == null) { footerNum = String.format("第 %d 頁, 共 %d 頁", currentPage, maxPage); fontBasePath = param.getFontBasePath() + "/PMingLiU-CN.ttf"; } else if ("E".equals(language)) { footerNum = String.format("Page %d , total %d pages", currentPage, maxPage); fontBasePath = param.getFontBasePath() + "/PMingLiU-CN.ttf"; } if (currentPage % 2 != 0) { // each bar code File barCodeFile = new File(param.getBarCodePath()); String barcode = genLeftBarCodeValue(currentPage,maxPage,LetterRecordCount); BarcodeUtil.genBarCode39(barcode, BarcodeUtil.IMG_TYPE_PNG, barCodeFile); // add left bar code addBarcode(document, param.getBarCodePath()); } if(currentPage==1 && Assert.isNotNull(bracodevalue)) { // bar code File barCodeFile = new File(param.getBarCodePath()); BarcodeUtil.genBarCode128(bracodevalue, BarcodeUtil.IMG_TYPE_PNG, barCodeFile); // add first page bar code addFirstPageBarcode(document, param.getBarCodePath()); } // setting font type BaseFont baseFont = BaseFont.createFont(fontBasePath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); Font fontDetail = new Font(baseFont, footerFontSize, Font.NORMAL); pdfContent.setFontAndSize(baseFont, footerFontSize); Phrase phrase = new Phrase(footerNum, fontDetail); // get file x pod float x = (document.left() + document.right()) / 2; // get file y pod float y = document.bottom(-10); // make page footer ColumnText.showTextAligned(pdfContent, Element.ALIGN_CENTER, phrase, x, y - 5, 0); // AC-Retention-C content Calendar calendar = Calendar.getInstance(); Date now = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); String footer = "AC-Retention-C-" + format.format(now); phrase = new Phrase(footer, fontDetail); ColumnText.showTextAligned(pdfContent, Element.ALIGN_CENTER, phrase, x + 200, y - 10, 0); pdfContent.endText(); pdfContent.restoreState(); } catch (Exception e) { e.printStackTrace(); } } /** * get left bar code value * @param currentPage * @param totalPage * @param LetterRecordCount * @return */ public static String genLeftBarCodeValue(Integer currentPage,Integer totalPage,Integer LetterRecordCount) { StringBuilder barcode = new StringBuilder(); if (currentPage.equals(totalPage)&¤tPage==1) { barcode.append("3"); }else if (currentPage==1) { barcode.append("2"); }else if(currentPage.equals(totalPage)) { barcode.append("1"); }else { barcode.append("0"); } //add field 2 (Letter Program Code (001-999)) barcode.append(String.format("%03d", 6)); //add field 3 (Letter records count (0001-9999)) barcode.append(String.format("%04d", LetterRecordCount+1)); //add field 4 (Insert value *Please check below table) barcode.append("1"); //add field 5 (Current Page Number) barcode.append(String.format("%02d", currentPage)); //add field 6 (total page) barcode.append(String.format("%02d", totalPage)); return barcode.toString(); } } |
多封pdf信封,设置关键之监听来重置PDF的页数KeyWordListener.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | import com.itextpdf.text.pdf.parser.ImageRenderInfo; import com.itextpdf.text.pdf.parser.RenderListener; import com.itextpdf.text.pdf.parser.TextRenderInfo; import hk.com.hkbn.ordletter.entity.LetterObj; /** * use to listener PDF file keywords */ public class KeyWordListener implements RenderListener { /** *match keyword */ private String keyword; /** * match current page */ private Integer pageNumber; /** * has match keyword object */ private LetterObj listenObj = new LetterObj(); @Override public void beginTextBlock() { //do nothing } @Override public void renderText(TextRenderInfo renderInfo) { //get char String content =renderInfo.getText(); if(content.contains(keyword)) { listenObj.setNextLetterNumber(true); listenObj.setLetterLangFlag(content.substring(content.length()-1)); } } @Override public void endTextBlock() { //do nothing } @Override public void renderImage(ImageRenderInfo renderInfo) { //do nothing } /** * setting need match keyword current page * @param pageNumber */ public void setPageNumber(Integer pageNumber) { this.pageNumber = pageNumber; } /** * setting match keyword * @param keyword */ public void setKeyword(String keyword) { this.keyword = keyword; } public LetterObj getListenObj() { return listenObj; } public void setListenObj(LetterObj listenObj) { this.listenObj = listenObj; } public String getKeyword() { return keyword; } public Integer getPageNumber() { return pageNumber; } } |
freemarker 通过模板文件生成html FreemarkerTemplate.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.Writer; import java.util.Locale; import java.util.Map; import freemarker.template.Configuration; import freemarker.template.Template; /** * freemarker make HTML * */ public class FreemarkerTemplate { private final Configuration configuration = new Configuration( Configuration.VERSION_2_3_23); private String charset; public FreemarkerTemplate(String charset) { this.charset = charset; configuration.setEncoding(Locale.CHINA, charset); configuration.setClassicCompatible(true);//处理空值未空字符串 } public void setTemplateClassPath(Class resourceLoaderClass, String basePackagePath) { configuration.setClassForTemplateLoading(resourceLoaderClass, basePackagePath); } public void setTemplateDirectoryPath(String templatePath) throws IOException { configuration.setDirectoryForTemplateLoading(new File(templatePath)); } public void processToStream(String templateFileName, Map<String, Object> dataMap, Writer writer) throws Throwable { Template template = configuration.getTemplate(templateFileName); template.process(dataMap, writer); } public void processToFile(String templateFileName, Map<String, Object> dataMap, File outFile) throws Throwable { Writer writer = new OutputStreamWriter(new FileOutputStream(outFile), charset); try { processToStream(templateFileName, dataMap, writer); } catch (Throwable e) { e.printStackTrace(); } finally { if (writer != null) { writer.close(); } } } public String processToString(String templateFileName, Map<String, Object> dataMap) throws Throwable { Writer writer = new StringWriter(2048); try { processToStream(templateFileName, dataMap, writer); return writer.toString(); } finally { if (writer != null) { writer.close(); } } } } |
java制作barcode39 和 barcode128 ,生活中比较常见的两种条形码BarcodeUtil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import org.krysalis.barcode4j.HumanReadablePlacement; import org.krysalis.barcode4j.impl.code128.Code128Bean; import org.krysalis.barcode4j.impl.code39.Code39Bean; import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider; import org.krysalis.barcode4j.tools.UnitConv; /** * make bar code * */ public class BarcodeUtil { public static final String IMG_TYPE_PNG="image/png"; public static final String IMG_TYPE_GIF="image/gif"; public static final String IMG_TYPE_JPEG="image/jpeg"; public static File genBarCode39(String msg,String imgType, File file) throws IOException { FileOutputStream out = new FileOutputStream(file); try { generateBarCode39(msg,imgType, out); } finally { if (out != null) { out.close(); } } return file; } public static byte[] generateToByte(String msg,String imgType) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { generateBarCode39(msg,imgType, out); return out.toByteArray(); } finally { if (out != null) { out.close(); } } } public static File genBarCode128(String msg,String imgType, File file) throws IOException { FileOutputStream out = new FileOutputStream(file); try { generateBarCode128(msg,imgType, out); } finally { if (out != null) { out.close(); } } return file; } public static void generateBarCode39(String msg, String imgType,OutputStream out) throws IOException { if (msg == null || out == null) { return; } Code39Bean bean = new Code39Bean(); int dpi = 150; // module width double moduleWidth = UnitConv.in2mm(1.0f / dpi); bean.setModuleWidth(moduleWidth); bean.setMsgPosition(HumanReadablePlacement.HRP_NONE); bean.setHeight(5); bean.setWideFactor(3); bean.doQuietZone(false); BitmapCanvasProvider canvas = new BitmapCanvasProvider(out, imgType, dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0); bean.generateBarcode(canvas, msg); canvas.finish(); } public static void generateBarCode128(String msg, String imgType,OutputStream out) throws IOException { if (msg == null || out == null) { return; } Code128Bean bean = new Code128Bean(); int dpi = 100; // module宽度 UnitConv.in2mm(1.f / dpi); double moduleWidth = 0.4; bean.setModuleWidth(moduleWidth); bean.setMsgPosition(HumanReadablePlacement.HRP_NONE); bean.setHeight(10); bean.doQuietZone(false); BitmapCanvasProvider canvas = new BitmapCanvasProvider(out, imgType, dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0); bean.generateBarcode(canvas, msg); canvas.finish(); } } |
分页的时候会出现的问题,内容断开,或者分页之后内容不对齐。
可以在css 中给div添加以下属性
1 2 3 4 | .gitf-name{ clear: both; display:inline-block;/*防止内容断开,将外层div设置为一个block*/ } |
部分freemarker 代码(有需要可以自己百度查一下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <!--first part--> <div class="container"> <div class="give_name">${letterPlan.customerName}</div> <div class="addr-first"> <div class="addtitle">BADD1</div> <div style="float: left;">${letterPlan.add1}</div> </div> <div class="addr"> <div class="addtitle">BADD2</div> <div style="float: left;">${letterPlan.add2}</div> </div> <div class="addr"> <div class="addtitle">BADD3</div> <div style="float: left;">${letterPlan.add3}</div> </div> <div class="addr"> <div class="addtitle">BADD4</div> <div style="float: left;">${letterPlan.add4}</div> </div> <div class="pps"> <div style="clear: both;display:inline-block;height: 5px;"> <div class="pps-left-div">${letterPlan.barcodeID}</div> <div class="pps-right-div">${letterPlan.customerBarcode}</div> </div> <div class="bar_code_div"> <!-- <img class="barcode" src="${barcodeImg}"></img> --> </div> <div class="send-data"> <div class="left-div">發信日期(年/月/日)</div> <div class="middle">:</div> <div class="right-div">${letterPlan.dateOfDispatch}</div> </div> <div class="useraccount"> <div class="left-div">賬戶號碼</div> <div class="middle">:</div> <div class="right-div">${letterPlan.accountPPS}</div> </div> </div> </div> |
最终效果图

