一、依赖
<!-- itext7 --><dependency><groupId>com.itextpdf</groupId><artifactId>kernel</artifactId><version>7.2.4</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>io</artifactId><version>7.2.4</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>forms</artifactId><version>7.2.4</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>layout</artifactId><version>7.2.4</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>svg</artifactId><version>7.2.4</version></dependency><!-- font-asian 用于itext使用东亚字体 --><dependency><groupId>com.itextpdf</groupId><artifactId>font-asian</artifactId><version>7.2.4</version></dependency><!-- html2pdf 用于转换html为pdf --><dependency><groupId>com.itextpdf</groupId><artifactId>html2pdf</artifactId><version>4.0.1</version></dependency>
二、代码
2.1、通用方法
/**** @param pdf_path PDF文件保存路径* @param pdf_name PDF文件名* @param cookie 用户信息* @param waterContent 水印内容* @return* @throws AppException*/public PmsLXPDF createHtmlToPDF(String pdf_path, String pdf_name, String cookie, String waterContent) throws AppException {logger.info("开始createPDF");PmsLXPDF pdf = new PmsLXPDF();String pdfViewUrl = "这里是接口路径,生成jsp模板";String url = Config.getMeurl();logger.info("createPDF方法获取meurl[{}]",url);ItextHtmlToPdf.convert(url + pdfViewUrl, pdf_path, cookie, waterContent);File file = new File(pdf_path);if (file == null || !file.exists()) {throw new AppException("立项报告生成pdf失败!");}try {//可以做一些保存的操作} catch (Exception e) {logger.error("Exception happened", e);}//pdf.setSuccess(StringUtils.isNotBlank(id));logger.info("createPDF结束");return pdf;}/*** html转pdf** @param srcPath html路径,可以是硬盘上的路径,也可以是网络路径* @param destPath pdf保存路径* @return 转换成功返回true*/public static boolean convert(String srcPath, String destPath, String cookie, String waterContent) {if (!FileUtils.createFile(destPath)) {LOGGER.error("目标路径PDF文件已存在,生成PDF失败:" + destPath);return false;}LOGGER.info("convert方法传入的srcPath[{}],cookie[{}]",srcPath,cookie);InputStream in = null;OutputStream out = null;HttpURLConnection connection = null;try {if (srcPath.startsWith("http")) {LOGGER.info("convert方法传入的srcPath[{}],进入了需要cookie的分支",srcPath);URL url = new URL(srcPath);int connectTimeout = Integer.parseInt(ConfigUtil.getConfig("xxx", "30000"));int readTimeout = Integer.parseInt(ConfigUtil.getConfig("xxx", "30000"));connection = (HttpURLConnection) url.openConnection();// 设置连接主机服务器的超时时间connection.setConnectTimeout(connectTimeout);// 设置读取远程返回的数据时间connection.setReadTimeout(readTimeout);if (StringUtils.isNotBlank(cookie)) {// 设置CookieString cookies = "SESSION=".concat(cookie);connection.setRequestProperty("Cookie", cookies);}// 连接和获取响应connection.connect();in = connection.getInputStream();} else {LOGGER.info("convert方法传入的srcPath[{}],进入了不需要登录的分支",srcPath);in = new FileInputStream(srcPath);}out = new ByteArrayOutputStream();HtmlPrittifier.fixHtml(in, out);} catch (Throwable t) {LOGGER.error("读取该文件失败:" + srcPath, t);return false;} finally {IOUtils.closeQuietly(in);IOUtils.closeQuietly(out);if (connection != null) {connection.disconnect();// 关闭远程连接}}String html = out.toString();converCreatPdf(destPath, cookie, html, waterContent);return true;}private static boolean converCreatPdf(String destPath, String cookie, String html, String waterContent) {try (OutputStream fos = new FileOutputStream(destPath)) {//将html转换成pdfPdfWriter pdfWriter = new PdfWriter(fos);PdfDocument pdfDoc = new PdfDocument(pdfWriter);// 统一设置页眉String header = "我是页眉";Header headerHandler = new Header(header);pdfDoc.addEventHandler(PdfDocumentEvent.START_PAGE, headerHandler);// 统一设置页脚Footer footerHandler = new Footer();pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, footerHandler);if (StringUtils.isNotBlank(waterContent)) {// 添加水印PdfWaterMarker pdfWaterMarker = new PdfWaterMarker(waterContent);pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, pdfWaterMarker);}// 需要先生成document,而不是直接生成pdf文件(因直接生成pdf文件会关闭流)ConverterProperties converterProperties = getConverterProperties(cookie);Document document = HtmlConverter.convertToDocument(html, pdfDoc, converterProperties);// flush触发写操作,此时才会触发已经注册的事件处理器document.flush();// 待document对象写完后,才能开始写入总页码LOGGER.debug("PDF总页数:" + document.getPdfDocument().getNumberOfPages());footerHandler.writeTotal(pdfDoc);document.close();} catch (Throwable t) {LOGGER.error("生成PDF失败:" + destPath, t);return false;}return true;}public static ConverterProperties getConverterProperties(String cookie) {ConverterProperties converterProperties = new ConverterProperties();FontProvider fontProvider = getFontProvider();converterProperties.setFontProvider(fontProvider);CookieResourceRetriever myResourceRetriever = new CookieResourceRetriever(cookie);converterProperties.setResourceRetriever(myResourceRetriever);return converterProperties;}public void writeTotal(PdfDocument pdf) {Canvas canvas = new Canvas(placeholder, pdf);canvas.setFontSize(8);if (font != null) {// 设置支持中文canvas.setFont(this.font);// 在占位符写入总页数canvas.showTextAligned(String.format("/共[%d]页", pdf.getNumberOfPages()), 0, descent,TextAlignment.LEFT);}canvas.close();}
2.2、工具类:
public class HtmlPrittifier {/*** 将HTML标准化,补全缺失的闭标签** @param in* @param out*/public static void fixHtml(InputStream in, OutputStream out) {// obtain a new Tidy instanceTidy tidy = new Tidy();// set desired config options using tidy setterstidy.setXHTML(true);tidy.setInputEncoding("utf8");tidy.setShowWarnings(true);tidy.setWraplen(1024);tidy.setSmartIndent(true);tidy.setQuiet(true);tidy.setPrintBodyOnly(false);tidy.setOutputEncoding("utf8");tidy.setTidyMark(false);// output document even if errors were found. 防止有些自定义tag匹配不上导致pdf不输出tidy.setForceOutput(true);// 不转换uri,防止uri中有中文,会将非ascii码转为十六进制,导致找不到中文图片链接。tidy.setFixUri(false);tidy.parse(in, out);}/*** 将HTML标准化,补全缺失的闭标签* * @param htmlString*/@SneakyThrowspublic static String fixHtml(String htmlString) {ByteArrayInputStream in = new ByteArrayInputStream(htmlString.getBytes("UTF-8"));ByteArrayOutputStream out = new ByteArrayOutputStream();fixHtml(in, out);return out.toString();}}
2.3、水印实现方法
private static PdfFont createDefaultFont() throws IOException {return PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");
}protected static class PdfWaterMarker implements IEventHandler {private PdfFont font;private String waterContent;public PdfWaterMarker(String waterContent) {this.waterContent = waterContent;try {this.font = createDefaultFont();} catch (IOException e) {LOGGER.error("PDF Header设置中文字体失败", e);}}@Overridepublic void handleEvent(Event event) {PdfDocumentEvent docEvent = (PdfDocumentEvent) event;PdfDocument pdfDoc = docEvent.getDocument();PdfPage page = docEvent.getPage();Rectangle pageSize = page.getPageSize();PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdfDoc);Canvas canvas = new Canvas(pdfCanvas, pageSize);// 设置水印文字内容Paragraph waterMarker = new Paragraph(waterContent).setFont(font).setOpacity(0.15f)// 设置透明度.setFontSize(16);// 文字大小/*for (int i = 0; i < 6; i++) {for (int j = 0; j < 6; j++) {canvas.showTextAligned(waterMarker, (150 + i * 300), (160 + j * 150), pdfDoc.getNumberOfPages(),TextAlignment.CENTER, VerticalAlignment.MIDDLE, 0.3f);}}*/// 计算水印的起始位置和间隔float xStart = 1;float yStart = 1;float stepX = 150;float stepY = 150;// 在页面上循环绘制水印文字for (float x = xStart; x < pageSize.getWidth(); x += stepX) {for (float y = yStart; y < pageSize.getHeight(); y += stepY) {// 绘制水印文字canvas.showTextAligned(waterMarker, x, y, pdfDoc.getNumberOfPages(),TextAlignment.LEFT, VerticalAlignment.MIDDLE, 0.6f);}}// 关闭流canvas.close();}}
2.4、页眉实现方法
private static PdfFont createDefaultFont() throws IOException {return PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");
}protected static class Header implements IEventHandler {private String header;private PdfFont font;public Header(String header) {this.header = header;try {this.font = createDefaultFont();} catch (IOException e) {LOGGER.error("PDF Header设置中文字体失败", e);}}@Overridepublic void handleEvent(Event event) {PdfDocumentEvent docEvent = (PdfDocumentEvent)event;PdfDocument pdf = docEvent.getDocument();PdfPage page = docEvent.getPage();Rectangle pageSize = page.getPageSize();Document doc = new Document(pdf);float leftMargin = doc.getLeftMargin();float rightMargin = doc.getRightMargin();float bottomMargin = doc.getBottomMargin();float topMargin = doc.getTopMargin();float height = pageSize.getHeight();float width = pageSize.getWidth();Canvas canvas = new Canvas(new PdfCanvas(page), pageSize);if (font != null) {// 设置支持中文canvas.setFont(this.font);}canvas.setFontSize(8);// Creates drawing canvasPdfCanvas pdfCanvas = new PdfCanvas(page);pdfCanvas.setLineWidth(0.1f);pdfCanvas.moveTo(leftMargin, height - topMargin + 3).lineTo(width - rightMargin, height - topMargin + 3).stroke();// Write text at positioncanvas.showTextAligned(header, width / 2, height - 30, TextAlignment.CENTER);canvas.close();}}
2.5、页脚实现方法
protected static class Footer implements IEventHandler {// 写总页码的占位符protected PdfFormXObject placeholder;// 页脚占位符大小private float side = 36;// 页脚占位符位置向右调整移动1,向下调整移动3private float space = 1;private float descent = 3;private PdfFont font;public Footer() {this.placeholder = new PdfFormXObject(new Rectangle(0, 0, side, side));try {this.font = createDefaultFont();} catch (IOException e) {LOGGER.error("PDF Footer设置中文字体失败", e);}}@Overridepublic void handleEvent(Event event) {PdfDocumentEvent docEvent = (PdfDocumentEvent)event;PdfDocument pdf = docEvent.getDocument();PdfPage page = docEvent.getPage();int pageNumber = pdf.getPageNumber(page);Rectangle pageSize = page.getPageSize();LOGGER.debug(String.format("当前处理第[%d]页", pageNumber));Document doc = new Document(pdf);float leftMargin = doc.getLeftMargin();float rightMargin = doc.getRightMargin();float bottomMargin = doc.getBottomMargin();float height = page.getPageSize().getHeight();float width = page.getPageSize().getWidth();// 页脚的位置float x = width / 2;float y = bottomMargin / 2;// Creates drawing canvasPdfCanvas pdfCanvas = new PdfCanvas(page);Canvas canvas = new Canvas(pdfCanvas, pageSize);if (font != null) {// 设置支持中文canvas.setFont(this.font);}canvas.setFontSize(8);// 设置支持横线// canvas.setStrokeWidth(0.1f);pdfCanvas.setLineWidth(0.1f);pdfCanvas.moveTo(leftMargin, bottomMargin - 3).lineTo(width - rightMargin, bottomMargin - 3).stroke();// 设置支持页码Paragraph p = new Paragraph().add(String.format("第[%d]页", pageNumber));canvas.showTextAligned(p, x, y, TextAlignment.RIGHT);canvas.close();// 添加占位符,用于写入总页码pdfCanvas.addXObjectAt(placeholder, x + space, y - descent);pdfCanvas.release();}
三、jsp实现水印(不影响之前已经生成模板的代码)
<style>#watermark div {/* color: rgba(255, 0, 0, .1); */color: rgba(0, 0, 255, .1);position: absolute;font-size: 24px;white-space: nowrap;transform: rotate(-30deg);-ms-transform: rotate(-30deg); /* IE 9 */-moz-transform: rotate(-30deg); /* Firefox */-webkit-transform: rotate(-30deg); /* Safari 和 Chrome */-o-transform: rotate(-30deg); /* Opera */}
</style>
<div id="watermark"style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: transparent; pointer-events: none; z-index: 99999999;">
</div>
<script>var operatorName = '${requestScope.xx.xxx}';var erpId = '${requestScope.xx.xxx}';(function() {var w = screen.width;var h = screen.height;var r = Math.sqrt(w * w + h * h);var i = 0;var j = 0;var watermark = document.querySelector('#watermark');var div;var top, left;for (var i = 0; i < 10; ++i) {for (var j = 0; j < 10; ++j) {div = document.createElement('div');div.innerText = '禁止外传:' + operatorName + '(' + erpId + ')';top = i * 100 + 'px';left = 500 * j - 50 - (i % 2 == 0 ? 250 : 0) + 'px';div.style.setProperty('top', top);div.style.setProperty('left', left);watermark.appendChild(div);}}})();
</script>