Java 实现Freemarker动态HTML转PDF 内带条形码
maven jar 准备
<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文件的页数及其他。
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
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
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
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
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添加以下属性
.gitf-name{
clear: both;
display:inline-block;/*防止内容断开,将外层div设置为一个block*/
}
部分freemarker 代码(有需要可以自己百度查一下)
<!--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>
最终效果图