1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 使用itext和freemarker来根据Html模板生成PDF文件 加水印 印章

使用itext和freemarker来根据Html模板生成PDF文件 加水印 印章

时间:2022-11-04 20:27:26

相关推荐

使用itext和freemarker来根据Html模板生成PDF文件 加水印 印章

功能:

实现根据freemarker模板生成对应的PDF文件;

可以指定文字、位置、页数生成指定的印章(图片),可以指定印章大小;

指定字体、字体大小、文字方向、颜色等生成文字水印

maven依赖:

<dependency><groupId>org.xhtmlrenderer</groupId><artifactId>flying-saucer-pdf-itext5</artifactId><version>9.1.18</version></dependency><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.27-incubating</version></dependency>

将html模板和数据进行匹配,然后用流的方式生成PDF文件

生成PDF工具类源码:

package com.lifengdi.file.pdf;import com.itextpdf.text.*;import com.itextpdf.text.pdf.*;import com.itextpdf.text.pdf.parser.PdfReaderContentParser;import com.lifengdi.config.GlobalConfig;import com.lifengdi.config.PDFFontConfig;import com.lifengdi.config.SystemConfig;import com.lifengdi.file.pdf.listener.MyTextLocationListener;import com.lifengdi.model.pdf.Location;import com.lifengdi.model.pdf.PDFStamperConfig;import com.lifengdi.model.pdf.PDFTempFile;import com.lifengdi.model.pdf.PDFWatermarkConfig;import com.lifengdi.util.GeneratedKey;import com.lifengdi.util.MyStringUtil;import freemarker.template.Configuration;import freemarker.template.Template;import freemarker.template.TemplateExceptionHandler;import lombok.extern.slf4j.Slf4j;import mons.io.FileUtils;import mons.lang3.StringUtils;import org.springframework.core.io.ClassPathResource;import org.ponent;import org.xhtmlrenderer.pdf.ITextFontResolver;import org.xhtmlrenderer.pdf.ITextRenderer;import javax.annotation.Resource;import java.io.*;import java.util.Objects;/*** 生成PDF工具类** @author 李锋镝* @date Create at 10:42 /5/9*/@Component@Slf4jpublic class HtmlToPDF {@Resourceprivate SystemConfig systemConfig;@Resourceprivate GeneratedKey generatedKey;/*** 模板和数据匹配** @param data 数据* @param pdfTempFile pdfTempFile* @return html*/public String matchDataToHtml(Object data, PDFTempFile pdfTempFile) {StringWriter writer = new StringWriter();String html;try {// FreeMarker配置Configuration config = new Configuration(Configuration.VERSION_2_3_25);config.setDefaultEncoding(GlobalConfig.DEFAULT_ENCODING);// 注意这里是模板所在文件夹,不是模版文件String parentPath, tempFileName = pdfTempFile.getTemplateFileName();if (MyStringUtil.isHttpUrl(pdfTempFile.getTemplateFileParentPath())) {parentPath = systemConfig.getLocalTempPath();} else {parentPath = pdfTempFile.getTemplateFileParentPath();// 将项目中的文件copy到服务器本地if (!parentPath.endsWith(File.separator))parentPath = parentPath + File.separator;String localTempPath = systemConfig.getLocalTempPath();String target = (localTempPath.endsWith(File.separator) ? localTempPath : localTempPath + File.separator) + tempFileName;InputStream tempFileInputStream = new ClassPathResource(parentPath + tempFileName).getInputStream();FileUtils.copyInputStreamToFile(tempFileInputStream, new File(target));}log.info("模板和数据匹配,模板文件parentPath:{}", parentPath);config.setDirectoryForTemplateLoading(new File(systemConfig.getLocalTempPath()));config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);config.setLogTemplateExceptions(false);// 根据模板名称 获取对应模板Template template = config.getTemplate(tempFileName);// 模板和数据的匹配template.process(data, writer);writer.flush();html = writer.toString();return html;} catch (Exception e) {log.error("PDF模板和数据匹配异常", e);} finally {try {writer.close();} catch (IOException e) {log.error("StringWriter close exception.", e);}}return null;}/*** 生成PDF** @param html html字符串* @param targetFileName 目标文件名* @return 生成文件的名称* @throws Exception e*/public String createPDF(String html, String targetFileName) throws Exception {if (StringUtils.isBlank(html)) {return null;}targetFileName = getFileName(targetFileName);log.info("生成PDF,targetFileName:{}", targetFileName);String targetFilePath = getTargetFileTempPath(targetFileName);FileOutputStream outFile = new FileOutputStream(targetFilePath);ITextRenderer renderer = new ITextRenderer();renderer.setDocumentFromString(html);// 解决中文支持问题log.info("加载字体");ITextFontResolver fontResolver = renderer.getFontResolver();fontResolver.addFont(SystemConfig.SourceHanSansCN_Regular_TTF, "SourceHanSansCN", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED, null);fontResolver.addFont(SystemConfig.FONT_PATH_SONG, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);renderer.layout();renderer.createPDF(outFile);log.info("生成PDF,targetFileName:{},生成成功", targetFileName);return targetFileName;}/*** 渲染文件** @param filePath PDF文件路径* @param outFilePath PDF文件路径* @param pdfStamperConfig pdfStamperConfig* @param pdfWatermarkConfig pdfWatermarkConfig*/public void renderLayer(String filePath, String outFilePath, PDFStamperConfig pdfStamperConfig, PDFWatermarkConfig pdfWatermarkConfig) {log.info("渲染文件,filePath:{},outFilePath:{}", filePath, outFilePath);InputStream inputStream = null;try {filePath = getTargetFileTempPath(filePath);inputStream = new FileInputStream(filePath);PdfReader reader = new PdfReader(inputStream);PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(getTargetFileTempPath(outFilePath)));if (Objects.nonNull(pdfStamperConfig) && pdfStamperConfig.getGenerate()) {// 印章log.info("添加印章,pdfStamperConfig:{}", pdfStamperConfig);stamper(pdfStamperConfig, reader, stamper);}if (Objects.nonNull(pdfWatermarkConfig) && pdfWatermarkConfig.getGenerate()) {// 水印log.info("添加水印,pdfWatermarkConfig:{}", pdfWatermarkConfig);watermark(reader, stamper, pdfWatermarkConfig);}stamper.close();reader.close();} catch (Exception e) {e.printStackTrace();} finally {try {if (null != inputStream) {inputStream.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 印章** @param pdfStamperConfig pdfStamperConfig* @param reader reader* @param stamperstamper* @throws IOException IOException* @throws DocumentException DocumentException*/private void stamper(PDFStamperConfig pdfStamperConfig, PdfReader reader, PdfStamper stamper) throws IOException, DocumentException {Image image;if (!MyStringUtil.isHttpUrl(pdfStamperConfig.getStamperUrl())) {// 将项目中的文件copy到服务器本地String imagePath = pdfStamperConfig.getStamperUrl();String imageName = imagePath.substring(imagePath.indexOf("/") + 1, imagePath.length());String localTempPath = systemConfig.getLocalTempPath();String target = (localTempPath.endsWith(File.separator) ? localTempPath : localTempPath + File.separator) + imageName;InputStream tempFileInputStream = new ClassPathResource(pdfStamperConfig.getStamperUrl()).getInputStream();FileUtils.copyInputStreamToFile(tempFileInputStream, new File(target));image = Image.getInstance(target);} else {image = Image.getInstance(pdfStamperConfig.getStamperUrl());}PdfReaderContentParser parser = new PdfReaderContentParser(reader);Document document = new Document();log.info("A4纸width:{},height:{}", document.getPageSize().getWidth(), document.getPageSize().getHeight());// // 取所在页和坐标,左下角为起点// float x = document.getPageSize().getWidth() - 240;// float y = document.getPageSize().getHeight() - 680;int pageSize = reader.getNumberOfPages();Location location = pdfStamperConfig.getLocation();if (Objects.nonNull(location)) {Integer page = location.getPage();// -1:全部,0:最后一页,1:首页if (Objects.nonNull(page)) {switch (page) {case -1:for (int pageNumber = 1; pageNumber <= pageSize; pageNumber++) {insertImage(pdfStamperConfig, stamper, image, parser, pageSize);}break;case 0:insertImage(pdfStamperConfig, stamper, image, parser, pageSize);break;default:if (page > 0) {insertImage(pdfStamperConfig, stamper, image, parser, page);}break;}}}}/*** 向指定位置插入图片** @param pdfStamperConfig pdfStamperConfig* @param stamperstamper* @param image image* @param parser parser* @param pageNumber pageNumber* @throws IOException IOException* @throws DocumentException DocumentException*/private void insertImage(PDFStamperConfig pdfStamperConfig, PdfStamper stamper, Image image, PdfReaderContentParser parser, int pageNumber)throws IOException, DocumentException {float x, y;Location xy = new Location();if (StringUtils.isNotBlank(pdfStamperConfig.getLocation().getWord())) {parser.processContent(pageNumber, new MyTextLocationListener(pdfStamperConfig, xy));} else {xy = pdfStamperConfig.getLocation();}if (Objects.isNull(xy)) {return;}if (Objects.isNull(xy.getX()) || Objects.isNull(xy.getY())) {return;}x = xy.getX();y = xy.getY();if (x < 0 || y < 0) {return;}// 读图片// 获取操作的页面PdfContentByte under = stamper.getOverContent(pageNumber);// 根据域的大小缩放图片// image.scaleToFit(Objects.isNull(pdfStamperConfig.getFitWidth()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_WIDTH : pdfStamperConfig.getFitWidth(),//Objects.isNull(pdfStamperConfig.getFitHeight()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_HEIGHT : pdfStamperConfig.getFitHeight());image.scaleAbsolute(Objects.isNull(pdfStamperConfig.getFitWidth()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_WIDTH : pdfStamperConfig.getFitWidth(),Objects.isNull(pdfStamperConfig.getFitHeight()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_HEIGHT : pdfStamperConfig.getFitHeight());// 添加图片image.setAbsolutePosition(x, y);under.addImage(image);}/*** 水印** @param reader reader* @param stamper stamper* @param pdfWatermarkConfig pdfWatermarkConfig*/private void watermark(PdfReader reader, PdfStamper stamper, PDFWatermarkConfig pdfWatermarkConfig) {if (Objects.isNull(pdfWatermarkConfig) || !pdfWatermarkConfig.getGenerate()) {return;}PdfContentByte under;// 字体BaseFont font = PDFFontConfig.FONT_MAP.get(PDFFontConfig.SIM_SUN);String fontFamily = pdfWatermarkConfig.getFontFamily();if (StringUtils.isNotBlank(fontFamily) && PDFFontConfig.FONT_MAP.containsKey(fontFamily)) {font = PDFFontConfig.FONT_MAP.get(fontFamily);}// 原pdf文件的总页数int pageSize = reader.getNumberOfPages();PdfGState gs = new PdfGState();// 设置填充字体不透明度为0.1fgs.setFillOpacity(0.1f);Document document = new Document();float documentWidth = document.getPageSize().getWidth(), documentHeight = document.getPageSize().getHeight();Location location = pdfWatermarkConfig.getLocation();if (Objects.isNull(location)) {location = new Location();}final float xStart = 0, yStart = 0,xInterval = Objects.nonNull(location.getXInterval()) ? location.getXInterval() : GlobalConfig.DEFAULT_X_INTERVAL,yInterval = Objects.nonNull(location.getYInterval()) ? location.getYInterval() : GlobalConfig.DEFAULT_Y_INTERVAL,rotation = 45,fontSize = Objects.isNull(pdfWatermarkConfig.getFontSize()) ? GlobalConfig.DEFAULT_FONT_SIZE : pdfWatermarkConfig.getFontSize();String watermarkWord = pdfWatermarkConfig.getWatermarkWord();int red = -1, green = -1, blue = -1;String[] colorArray = pdfWatermarkConfig.getWatermarkColor().split(",");if (colorArray.length >= 3) {red = Integer.parseInt(colorArray[0]);green = Integer.parseInt(colorArray[1]);blue = Integer.parseInt(colorArray[2]);}for (int i = 1; i <= pageSize; i++) {// 水印在之前文本下if (Objects.nonNull(location.getOverContent()) && location.getOverContent()) {under = stamper.getOverContent(i);} else {under = stamper.getUnderContent(i);}under.beginText();// 文字水印 颜色if (red >= 0) {under.setColorFill(new BaseColor(red, green, blue));} else {under.setColorFill(BaseColor.GRAY);}// 文字水印 字体及字号under.setFontAndSize(font, fontSize);under.setGState(gs);// 文字水印 起始位置under.setTextMatrix(xStart, yStart);if (StringUtils.isNotBlank(watermarkWord)) {for (float x = xStart; x <= documentWidth + xInterval; x += xInterval) {for (float y = yStart; y <= documentHeight + yInterval; y += yInterval) {under.showTextAligned(Element.ALIGN_CENTER, watermarkWord, x, y, rotation);}}}under.endText();}}/*** 获取生成的PDF文件本地临时路径** @param targetFileName 目标文件名* @return 本地临时路径*/public String getTargetFileTempPath(String targetFileName) {String localTempPath = systemConfig.getLocalTempPath();if (!localTempPath.endsWith(File.separator)) {localTempPath = localTempPath + File.separator;}return localTempPath + targetFileName;}private String getFileName(String targetFileName) {if (StringUtils.isBlank(targetFileName)) {targetFileName = generatedKey.generatorKey();}if (!StringUtils.endsWithIgnoreCase(targetFileName, GlobalConfig.PDF_SUFFIX)) {targetFileName = targetFileName + GlobalConfig.PDF_SUFFIX;}return targetFileName;}}

获取指定文字坐标,用来在指定文字上生成印章,主要实现了RenderListener来获取指定文字的坐标。

源码:

package com.lifengdi.file.pdf.listener;import com.itextpdf.awt.geom.Rectangle2D;import com.itextpdf.text.pdf.parser.ImageRenderInfo;import com.itextpdf.text.pdf.parser.RenderListener;import com.itextpdf.text.pdf.parser.TextRenderInfo;import com.lifengdi.config.GlobalConfig;import com.lifengdi.model.pdf.Location;import com.lifengdi.model.pdf.PDFStamperConfig;import lombok.extern.slf4j.Slf4j;import mons.lang3.StringUtils;import java.util.Objects;/*** @author 李锋镝* @date Create at 15:32 /5/9*/@Slf4jpublic class MyTextLocationListener implements RenderListener {private String text;private PDFStamperConfig pdfStamperConfig;private Location location;public MyTextLocationListener(PDFStamperConfig pdfStamperConfig, Location location) {if (StringUtils.isNotBlank(pdfStamperConfig.getLocation().getWord())) {this.text = pdfStamperConfig.getLocation().getWord();}this.pdfStamperConfig = pdfStamperConfig;this.location = location;}@Overridepublic void beginTextBlock() {}@Overridepublic void renderText(TextRenderInfo renderInfo) {String renderInfoText = renderInfo.getText();if (!StringUtils.isEmpty(renderInfoText) && renderInfoText.contains(text)) {Rectangle2D.Float base = renderInfo.getBaseline().getBoundingRectange();float leftX = (float) base.getMinX();float leftY = (float) base.getMinY() - 1;float rightX = (float) base.getMaxX();float rightY = (float) base.getMaxY() + 1;Rectangle2D.Float rect = new Rectangle2D.Float(leftX, leftY, rightX - leftX, rightY - leftY);// 当前行长度int length = renderInfoText.length();// 单个字符的长度float wordWidth = rect.width / length;// 指定字符串首次出现的索引int i = renderInfoText.indexOf(text);Float fitWidth = Objects.isNull(pdfStamperConfig.getFitWidth()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_WIDTH : pdfStamperConfig.getFitWidth();Float fitHeight = Objects.isNull(pdfStamperConfig.getFitHeight()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_HEIGHT : pdfStamperConfig.getFitHeight();float fitWidthRadius = 0f, fitHeightRadius = 0f;if (fitWidth > 0) {fitWidthRadius = fitWidth / 2;}if (fitHeight > 0) {fitHeightRadius = fitHeight / 2;}// 偏移量Float xOffset = Objects.isNull(pdfStamperConfig.getXOffset()) ? 0F : pdfStamperConfig.getXOffset();Float yOffset = Objects.isNull(pdfStamperConfig.getYOffset()) ? 0F : pdfStamperConfig.getYOffset();// 设置印章的XY坐标float x, y;if (rect.x < 60) {x = wordWidth * i + fitHeightRadius + xOffset;} else {x = rect.x + xOffset;}y = rect.y - fitWidthRadius + yOffset;location.setY(y > 0 ? y : 0);location.setX(x > 0 ? x : 0);log.info("text:{}, location:{}", text, location);}}@Overridepublic void endTextBlock() {}@Overridepublic void renderImage(ImageRenderInfo renderInfo) {}}

其他配置类:

package com.lifengdi.model.pdf;import lombok.Data;/*** PDF模板配置* @author 李锋镝* @date Create at 11:10 /5/9*/@Datapublic class PDFTempFile {/*** PDF模板文件类型*/private String pdfType;/*** 模板文件名称*/private String templateFileName;/*** 模板文件所在父级路径*/private String templateFileParentPath;/*** 模板文件本地地址*/private String templateFileLocalPath;}

package com.lifengdi.config;import lombok.Getter;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;/*** @author 李锋镝* @date Create at 10:51 /5/9*/@Getter@Configurationpublic class SystemConfig {/*** 文件本地缓存目录*/@Value("${file.localTempPath}")private String localTempPath;/*** 项目中自带字体的目录*/public static final String FONT_PATH_SONG = "font/simsun.ttf";public static final String SourceHanSansCN_Regular_TTF = "font/SourceHanSansCN-Regular.ttf";/*** 项目中的缓存目录*/@Value("${file.appTempFolder:file/}")private String appTemplateFolder;}

package com.lifengdi.model.pdf;import lombok.Data;/*** PDF印章配置* @author 李锋镝* @date Create at 11:16 /5/9*/@Datapublic class PDFStamperConfig {/*** 是否生成印章*/private Boolean generate = false;/*** 印章文件路径(PNG格式)*/private String stamperUrl;/*** 生成印章的位置*/private Location location;/*** 印章宽度*/private Float fitWidth;/*** 印章高度*/private Float fitHeight;/*** X偏移量*/private Float xOffset;/*** Y偏移量*/private Float yOffset;}

package com.lifengdi.model.pdf;import lombok.Data;/*** PDF文件水印配置* @author 李锋镝* @date Create at 11:17 /5/9*/@Datapublic class PDFWatermarkConfig {/*** 是否生成水印*/private Boolean generate;/*** 水印文字*/private String watermarkWord;/*** 水印文字颜色 red,green,blue*/private String watermarkColor = "128,128,128";/*** 水印透明度*/private Float fillOpacity = 0.1F;/*** 水印文字大小*/private Integer fontSize = 38;/*** 水印文字字体*/private String fontFamily;/*** 生成水印的位置,不指定则默认整页*/private Location location;}

package com.lifengdi.model.pdf;import lombok.Data;/*** 页面位置坐标* @author 李锋镝* @date Create at 11:35 /5/9*/@Datapublic class Location {/*** X坐标*/private Float x;/*** Y坐标*/private Float y;/*** 横向间隔*/private Float xInterval;/*** 纵向间隔*/private Float yInterval;/*** 指定页 -1:全部,0:最后一页,1:首页*/private Integer page;/*** 在指定文字上盖章*/private String word;/*** 水印是否在内容上边*/private Boolean overContent;}

package com.lifengdi.model.pdf;import com.lifengdi.model.IType;import lombok.Data;/*** @author 李锋镝* @date Create at 14:07 /5/9*/@Datapublic class PDFType implements IType {private PDFTempFile pdfTempFile;private PDFStamperConfig pdfStamperConfig;private PDFWatermarkConfig pdfWatermarkConfig;}

package com.lifengdi.config;import com.lifengdi.model.pdf.PDFType;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.ponent;import java.util.HashMap;import java.util.Map;/*** PDF配置* @author 李锋镝* @date Create at 11:10 /5/9*/@Data@Component@ConfigurationProperties("pdf")public class PDFConfig {private Map<String, PDFType> pdfType = new HashMap<>();}

在application.yml配置如下:

# 生成PDF文件配置pdf:pdfType:test:pdfTempFile:pdfType: testtemplateFileName: test.ftltemplateFileParentPath: file/pdfStamperConfig:generate: true# 印章所在路径stamperUrl: image/666.pngxOffset: 0yOffset: 45fitWidth: 120fitHeight: 120location:x: -1y: -1page: 1word: 检测日期pdfWatermarkConfig:generate: truewatermarkWord: 水印watermarkColor:fillOpacity:fontSize:fontFamily: SourceHanSansCNlocation:xInterval: 100yInterval: 120page:overContent: true

生成PDF效果截图:

源码地址:/lifengdi/generate-file

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:/archives/article/tech/861

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。