使用 flying-saucer-pdf-itext5加freemarker生成pdf,支持中文、图片水印+文字水印、页眉页脚。
引入jar包
org.springframework.boot
spring-boot-starter-freemarker
org.xhtmlrenderer
flying-saucer-pdf-itext5
9.1.20
org.jsoup
jsoup
1.13.1
PDF转换工具类:
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.util.XRRuntimeException;
import java.io.*;
import java.util.Map;
/**
* 导出PDF工具类
*/
public class PDFUtil {
private PDFUtil() {
}
private volatile static Configuration configuration;
static {
if (configuration == null) {
synchronized (PDFUtil.class) {
if (configuration == null) {
configuration = new Configuration(Configuration.VERSION_2_3_28);
}
}
}
}
/**
* freemarker 通过流的方式 引擎渲染 html
*
* @param dataMap 传入 html 模板的 Map 数据
* @param ftlFilePath html 模板文件相对路径(相对于 resources路径,路径 + 文件名),之后通过 BufferedReader 流来读取模板
* eg: "templates/pdf_export_demo.ftl"
* @return
*/
public static String freemarkerRender(Map dataMap, String ftlFilePath) {
configuration.setDefaultEncoding("UTF-8");
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
configuration.setLogTemplateExceptions(false);
configuration.setWrapUncheckedExceptions(true);
Writer out = new StringWriter();
try {
Template template = new Template("", PDFUtil.returnReaderStream(ftlFilePath), configuration);
template.process(dataMap, out);
out.flush();
return out.toString();
} catch (IOException | TemplateException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 使用 iText 生成 PDF 文档
*
* @param htmlTmpStr html 模板文件字符串
* @param fontFilePath 所需字体文件(相对路径+文件名)
* @param waterImgPath 水印图片路径
* @param waterContent 水印文字内容
*/
public static byte[] createPDF(String htmlTmpStr, String fontFilePath, String waterImgPath, String waterContent) {
ByteArrayOutputStream outputStream = null;
byte[] result = null;
try {
outputStream = new ByteArrayOutputStream();
ITextRendererO renderer = new ITextRendererO();
renderer.setDocumentFromString(htmlTmpStr);
ITextFontResolver fontResolver = renderer.getFontResolver();
// 解决中文支持问题,需要所需字体(ttc)文件
fontResolver.addFont(fontFilePath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
renderer.layout();
renderer.createPDF(outputStream, fontFilePath, waterImgPath, waterContent);
renderer.finishPDF();
result = outputStream.toByteArray();
} catch (IOException | XRRuntimeException | DocumentException e) {
e.printStackTrace();
return null;
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* 根据文件相对路径返回一个 BufferedReader 流
*
* @param filePath
* @return
* @throws IOException
*/
public static BufferedReader returnReaderStream(String filePath) throws IOException {
return new BufferedReader(new InputStreamReader(new ClassPathResource(filePath).getInputStream()));
}
/**
* 根据文件相对路径返回一个 BufferedReader 流
*
* @param filePath
* @return
* @throws IOException
*/
public static InputStream returnInputStream(String filePath) throws IOException {
return new ClassPathResource(filePath).getInputStream();
}
}
水印页眉页码生成:
重写ITextRenderer类
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xhtmlrenderer.context.StyleReference;
import org.xhtmlrenderer.css.style.CalculatedStyle;
import org.xhtmlrenderer.css.style.derived.RectPropertySet;
import org.xhtmlrenderer.extend.NamespaceHandler;
import org.xhtmlrenderer.extend.UserInterface;
import org.xhtmlrenderer.layout.BoxBuilder;
import org.xhtmlrenderer.layout.Layer;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.pdf.*;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.render.PageBox;
import org.xhtmlrenderer.render.RenderingContext;
import org.xhtmlrenderer.render.ViewportBox;
import org.xhtmlrenderer.resource.XMLResource;
import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler;
import org.xhtmlrenderer.util.Configuration;
import org.xml.sax.InputSource;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.awt.*;
import java.io.*;
import java.util.List;
import java.util.regex.Pattern;
public class ITextRendererO extends ITextRenderer {
// These two defaults combine to produce an effective resolution of 96 px to
// the inch
private static final float DEFAULT_DOTS_PER_POINT = 20f * 4f / 3f;
private static final int DEFAULT_DOTS_PER_PIXEL = 20;
private final SharedContext _sharedContext;
private final ITextOutputDevice _outputDevice;
private Document _doc;
private BlockBox _root;
private final float _dotsPerPoint;
private com.itextpdf.text.Document _pdfDoc;
private PdfWriter _writer;
private PDFEncryption _pdfEncryption;
// note: not hard-coding a default version in the _pdfVersion field as this
// may change between iText releases
// check for null before calling writer.setPdfVersion()
// use one of the values in PDFWriter.VERSION...
private Character _pdfVersion;
private Dimension _dim;
private boolean scaleToFit;
private final char[] validPdfVersions = new char[]{PdfWriter.VERSION_1_2, PdfWriter.VERSION_1_3, PdfWriter.VERSION_1_4,
PdfWriter.VERSION_1_5, PdfWriter.VERSION_1_6, PdfWriter.VERSION_1_7};
private PDFCreationListener _listener;
public ITextRendererO() {
this(DEFAULT_DOTS_PER_POINT, DEFAULT_DOTS_PER_PIXEL);
}
public ITextRendererO(float dotsPerPoint, int dotsPerPixel) {
_dotsPerPoint = dotsPerPoint;
_outputDevice = new ITextOutputDevice(_dotsPerPoint);
ITextUserAgent userAgent = new ITextUserAgent(_outputDevice);
_sharedContext = new SharedContext();
_sharedContext.setUserAgentCallback(userAgent);
_sharedContext.setCss(new StyleReference(userAgent));
userAgent.setSharedContext(_sharedContext);
_outputDevice.setSharedContext(_sharedContext);
ITextFontResolver fontResolver = new ITextFontResolver(_sharedContext);
_sharedContext.setFontResolver(fontResolver);
ITextReplacedElementFactory replacedElementFactory = new ITextReplacedElementFactory(_outputDevice);
_sharedContext.setReplacedElementFactory(replacedElementFactory);
_sharedContext.setTextRenderer(new ITextTextRenderer());
_sharedContext.setDPI(72 * _dotsPerPoint);
_sharedContext.setDotsPerPixel(dotsPerPixel);
_sharedContext.setPrint(true);
_sharedContext.setInteractive(false);
}
public Document getDocument() {
return _doc;
}
public ITextFontResolver getFontResolver() {
return (ITextFontResolver) _sharedContext.getFontResolver();
}
private Document loadDocument(final String uri) {
return _sharedContext.getUac().getXMLResource(uri).getDocument();
}
public void setDocument(String uri) {
setDocument(loadDocument(uri), uri);
}
public void setDocument(Document doc, String url) {
setDocument(doc, url, new XhtmlNamespaceHandler());
}
public void setDocument(File file) throws IOException {
File parent = file.getAbsoluteFile().getParentFile();
setDocument(loadDocument(file.toURI().toURL().toExternalForm()), (parent == null ? "" : parent.toURI().toURL().toExternalForm()));
}
public void setDocument(byte[] bytes) throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
Document dom = XMLResource.load(inputStream).getDocument();
setDocument(dom, "");
}
public void setDocumentFromString(String content) {
setDocumentFromString(content, null);
}
public void setDocumentFromString(String content, String baseUrl) {
InputSource is = new InputSource(new BufferedReader(new StringReader(content)));
Document dom = XMLResource.load(is).getDocument();
setDocument(dom, baseUrl);
}
public void setDocument(Document doc, String url, NamespaceHandler nsh) {
_doc = doc;
getFontResolver().flushFontFaceFonts();
_sharedContext.reset();
if (Configuration.isTrue("xr.cache.stylesheets", true)) {
_sharedContext.getCss().flushStyleSheets();
} else {
_sharedContext.getCss().flushAllStyleSheets();
}
_sharedContext.setBaseURL(url);
_sharedContext.setNamespaceHandler(nsh);
_sharedContext.getCss().setDocumentContext(_sharedContext, _sharedContext.getNamespaceHandler(), doc, new NullUserInterface());
getFontResolver().importFontFaces(_sharedContext.getCss().getFontFaceRules());
}
public PDFEncryption getPDFEncryption() {
return _pdfEncryption;
}
public void setPDFEncryption(PDFEncryption pdfEncryption) {
_pdfEncryption = pdfEncryption;
}
public void setPDFVersion(char _v) {
for (int i = 0; i < validPdfVersions.length; i++) {
if (_v == validPdfVersions[i]) {
_pdfVersion = new Character(_v);
return;
}
}
throw new IllegalArgumentException("Invalid PDF version character; use "
+ "valid constants from PdfWriter (e.g. PdfWriter.VERSION_1_2)");
}
public char getPDFVersion() {
return _pdfVersion == null ? '0' : _pdfVersion.charValue();
}
public void layout() {
LayoutContext c = newLayoutContext();
BlockBox root = BoxBuilder.createRootBox(c, _doc);
root.setContainingBlock(new ViewportBox(getInitialExtents(c)));
root.layout(c);
_dim = root.getLayer().getPaintingDimension(c);
root.getLayer().trimEmptyPages(c, _dim.height);
root.getLayer().layoutPages(c);
_root = root;
}
private Rectangle getInitialExtents(LayoutContext c) {
PageBox first = Layer.createPageBox(c, "first");
return new Rectangle(0, 0, first.getContentWidth(c), first.getContentHeight(c));
}
private RenderingContext newRenderingContext() {
RenderingContext result = _sharedContext.newRenderingContextInstance();
result.setFontContext(new ITextFontContext());
result.setOutputDevice(_outputDevice);
_sharedContext.getTextRenderer().setup(result.getFontContext());
result.setRootLayer(_root.getLayer());
return result;
}
private LayoutContext newLayoutContext() {
LayoutContext result = _sharedContext.newLayoutContextInstance();
result.setFontContext(new ITextFontContext());
_sharedContext.getTextRenderer().setup(result.getFontContext());
return result;
}
public void writeNextDocument() throws DocumentException, IOException {
writeNextDocument(0);
}
public void writeNextDocument(int initialPageNo) throws DocumentException, IOException {
List pages = _root.getLayer().getPages();
RenderingContext c = newRenderingContext();
c.setInitialPageNo(initialPageNo);
PageBox firstPage = (PageBox) pages.get(0);
com.itextpdf.text.Rectangle firstPageSize = new com.itextpdf.text.Rectangle(0, 0, firstPage.getWidth(c) / _dotsPerPoint,
firstPage.getHeight(c) / _dotsPerPoint);
_outputDevice.setStartPageNo(_writer.getPageNumber());
_pdfDoc.setPageSize(firstPageSize);
_pdfDoc.newPage();
writePDF(pages, c, firstPageSize, _pdfDoc, _writer);
}
public void finishPDF() {
if (_pdfDoc != null) {
fireOnClose();
_pdfDoc.close();
}
}
/**
* @param os pdf输出流
* @param fontFilePath 文字文件路径
* @param waterImgPath 水印图片路径
* @param waterContent 水印内容
* @throws DocumentException
*/
public void createPDF(OutputStream os, String fontFilePath, String waterImgPath, String waterContent) throws DocumentException, IOException {
createPDF(os, true, 0, fontFilePath, waterImgPath, waterContent);
}
/**
* NOTE: Caller is responsible for cleaning up the OutputStream if
* something goes wrong.
*
* @throws IOException
*/
public void createPDF(OutputStream os, boolean finish, int initialPageNo, String fontFilePath, String waterImgPath, String waterContent) throws DocumentException, IOException {
List pages = _root.getLayer().getPages();
RenderingContext c = newRenderingContext();
c.setInitialPageNo(initialPageNo);
PageBox firstPage = (PageBox) pages.get(0);
int pageWidth = calculateWidth(c, firstPage);
com.itextpdf.text.Rectangle firstPageSize = new com.itextpdf.text.Rectangle(0, 0, pageWidth / _dotsPerPoint,
firstPage.getHeight(c) / _dotsPerPoint);
com.itextpdf.text.Document doc = new com.itextpdf.text.Document(firstPageSize, 0, 0, 0, 0);
PdfWriter writer = PdfWriter.getInstance(doc, os);
// 添加水印和页码
PDFBuilder builder = new PDFBuilder(fontFilePath , waterImgPath, waterContent);
writer.setPageEvent(builder);
if (_pdfVersion != null) {
writer.setPdfVersion(_pdfVersion.charValue());
}
if (_pdfEncryption != null) {
writer.setEncryption(_pdfEncryption.getUserPassword(), _pdfEncryption.getOwnerPassword(),
_pdfEncryption.getAllowedPrivileges(), _pdfEncryption.getEncryptionType());
}
_pdfDoc = doc;
_writer = writer;
firePreOpen();
doc.open();
writePDF(pages, c, firstPageSize, doc, writer);
if (finish) {
fireOnClose();
doc.close();
}
}
private int calculateWidth(RenderingContext c, PageBox firstPage) {
if (isScaleToFit()) {
int pageWidth = firstPage.getWidth(c);
Rectangle pageRec = firstPage.getPrintClippingBounds(c);
if (_dim.getWidth() > pageRec.getWidth()) {
RectPropertySet margin = firstPage.getMargin(c);
pageWidth = (int) (_dim.getWidth() + margin.left() + margin.right());
}
return pageWidth;
} else {
return firstPage.getWidth(c);
}
}
private void firePreOpen() {
if (_listener != null) {
_listener.preOpen(this);
}
}
private void firePreWrite(int pageCount) {
if (_listener != null) {
_listener.preWrite(this, pageCount);
}
}
private void fireOnClose() {
if (_listener != null) {
_listener.onClose(this);
}
}
private void writePDF(List pages, RenderingContext c, com.itextpdf.text.Rectangle firstPageSize, com.itextpdf.text.Document doc,
PdfWriter writer) throws DocumentException, IOException {
_outputDevice.setRoot(_root);
_outputDevice.start(_doc);
_outputDevice.setWriter(writer);
_outputDevice.initializePage(writer.getDirectContent(), firstPageSize.getHeight());
_root.getLayer().assignPagePaintingPositions(c, Layer.PAGED_MODE_PRINT);
int pageCount = _root.getLayer().getPages().size();
c.setPageCount(pageCount);
firePreWrite(pageCount); // opportunity to adjust meta data
setDidValues(doc); // set PDF header fields from meta data
for (int i = 0; i < pageCount; i++) {
PageBox currentPage = (PageBox) pages.get(i);
c.setPage(i, currentPage);
paintPage(c, writer, currentPage);
_outputDevice.finishPage();
if (i != pageCount - 1) {
PageBox nextPage = (PageBox) pages.get(i + 1);
int pageWidth = calculateWidth(c, nextPage);
com.itextpdf.text.Rectangle nextPageSize = new com.itextpdf.text.Rectangle(0, 0, pageWidth / _dotsPerPoint,
nextPage.getHeight(c) / _dotsPerPoint);
// com.itextpdf.text.Rectangle nextPageSize = new com.itextpdf.text.Rectangle(0, 0, nextPage.getWidth(c) / _dotsPerPoint,
// nextPage.getHeight(c) / _dotsPerPoint);
doc.setPageSize(nextPageSize);
doc.newPage();
_outputDevice.initializePage(writer.getDirectContent(), nextPageSize.getHeight());
}
}
_outputDevice.finish(c, _root);
}
// Sets the document information dictionary values from html metadata
private void setDidValues(com.itextpdf.text.Document doc) {
String v = _outputDevice.getMetadataByName("title");
if (v != null) {
doc.addTitle(v);
}
v = _outputDevice.getMetadataByName("author");
if (v != null) {
doc.addAuthor(v);
}
v = _outputDevice.getMetadataByName("subject");
if (v != null) {
doc.addSubject(v);
}
v = _outputDevice.getMetadataByName("keywords");
if (v != null) {
doc.addKeywords(v);
}
}
private void paintPage(RenderingContext c, PdfWriter writer, PageBox page) throws IOException {
provideMetadataToPage(writer, page);
page.paintBackground(c, 0, Layer.PAGED_MODE_PRINT);
page.paintMarginAreas(c, 0, Layer.PAGED_MODE_PRINT);
page.paintBorder(c, 0, Layer.PAGED_MODE_PRINT);
Shape working = _outputDevice.getClip();
Rectangle content = page.getPrintClippingBounds(c);
if (isScaleToFit()) {
int pageWidth = calculateWidth(c, page);
content.setSize(pageWidth, (int) content.getSize().getHeight());//RTD - to change
}
_outputDevice.clip(content);
int top = -page.getPaintingTop() + page.getMarginBorderPadding(c, CalculatedStyle.TOP);
int left = page.getMarginBorderPadding(c, CalculatedStyle.LEFT);
_outputDevice.translate(left, top);
_root.getLayer().paint(c);
_outputDevice.translate(-left, -top);
_outputDevice.setClip(working);
}
private void provideMetadataToPage(PdfWriter writer, PageBox page) throws IOException {
byte[] metadata = null;
if (page.getMetadata() != null) {
try {
String metadataBody = stringfyMetadata(page.getMetadata());
if (metadataBody != null) {
metadata = createXPacket(stringfyMetadata(page.getMetadata())).getBytes("UTF-8");
}
} catch (UnsupportedEncodingException e) {
// Can't happen
throw new RuntimeException(e);
}
}
if (metadata != null) {
writer.setPageXmpMetadata(metadata);
}
}
private String stringfyMetadata(Element element) {
Element target = getFirstChildElement(element);
if (target == null) {
return null;
}
try {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
StringWriter output = new StringWriter();
transformer.transform(new DOMSource(target), new StreamResult(output));
return output.toString();
} catch (TransformerConfigurationException e) {
// Things must be in pretty bad shape to get here so
// rethrow as runtime exception
throw new RuntimeException(e);
} catch (TransformerException e) {
throw new RuntimeException(e);
}
}
private static Element getFirstChildElement(Element element) {
Node n = element.getFirstChild();
while (n != null) {
if (n.getNodeType() == Node.ELEMENT_NODE) {
return (Element) n;
}
n = n.getNextSibling();
}
return null;
}
private String createXPacket(String metadata) {
StringBuffer result = new StringBuffer(metadata.length() + 50);
result.append("\n");
result.append(metadata);
result.append("\n");
return result.toString();
}
public ITextOutputDevice getOutputDevice() {
return _outputDevice;
}
public SharedContext getSharedContext() {
return _sharedContext;
}
public void exportText(Writer writer) throws IOException {
RenderingContext c = newRenderingContext();
c.setPageCount(_root.getLayer().getPages().size());
_root.exportText(c, writer);
}
public BlockBox getRootBox() {
return _root;
}
public float getDotsPerPoint() {
return _dotsPerPoint;
}
public List findPagePositionsByID(Pattern pattern) {
return _outputDevice.findPagePositionsByID(newLayoutContext(), pattern);
}
private static final class NullUserInterface implements UserInterface {
public boolean isHover(Element e) {
return false;
}
public boolean isActive(Element e) {
return false;
}
public boolean isFocus(Element e) {
return false;
}
}
public PDFCreationListener getListener() {
return _listener;
}
public void setListener(PDFCreationListener listener) {
_listener = listener;
}
public PdfWriter getWriter() {
return _writer;
}
public boolean isScaleToFit() {
return scaleToFit;
}
public boolean setScaleToFit(boolean scaleToFit) {
return this.scaleToFit = scaleToFit;
}
}
生成水印和页眉页码的事件
package cn.cooptec.admin.core.utils;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.InputStream;
/**
* 设置页面附加属性
*/
public class PDFBuilder extends PdfPageEventHelper {
/**
* 页眉描述
*/
private String header = "我是页眉";
/**
* 水印图片地址
*/
private String waterImgPath;
/**
* 水印内容
*/
private String waterContent;
/**
* 文档字体大小,页脚页眉最好和文本大小一致
*/
private int presentFontSize = 12;
// 基础字体对象
private BaseFont bf = null;
// 利用基础字体生成的字体对象,一般用于生成中文文字
private Font fontDetail = null;
/**
* Creates a new instance of PdfReportM1HeaderFooter 无参构造方法.
*/
public PDFBuilder() {
}
public PDFBuilder(String fontFilePath, String waterImgPath, String waterContent) {
try {
if (StringUtils.isNotBlank(fontFilePath)) {
// 设置文字中文字体
this.bf = BaseFont.createFont(fontFilePath + ",1", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED, false);
this.fontDetail = new Font(this.bf, presentFontSize, Font.NORMAL);
} else {
//设置分页页眉页脚字体
if (bf == null) {
bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", false);
}
if (fontDetail == null) {
fontDetail = new Font(bf, presentFontSize, Font.NORMAL); // 数据体字体
}
}
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
this.waterImgPath = waterImgPath;
this.waterContent = waterContent;
}
public void setHeader(String header) {
this.header = header;
}
public void setPresentFontSize(int presentFontSize) {
this.presentFontSize = presentFontSize;
}
/**
* 文档打开时
*/
@Override
public void onOpenDocument(PdfWriter writer, Document document) {
}
/**
* 关闭每页的时候,写入页眉,写入'第几页'这几个字。
*
* @see com.itextpdf.text.pdf.PdfPageEventHelper#onEndPage(com.itextpdf.text.pdf.PdfWriter,
* com.itextpdf.text.Document)
*/
@Override
public void onEndPage(PdfWriter writer, Document document) {
this.addPageHeaderFooter(writer, document);
this.addWatermark(writer, document);
}
// 添加页眉页脚
private void addPageHeaderFooter(PdfWriter writer, Document document) {
int pageS = writer.getPageNumber();
if (pageS != 1) { // 封面不展示页眉页脚
pageS = pageS - 1;
float len1 = bf.getWidthPoint(header, presentFontSize);
// 1.写入页眉
ColumnText.showTextAligned(writer.getDirectContent(),
Element.ALIGN_CENTER, new Phrase(header, fontDetail),
(document.rightMargin() + document.right() + document.leftMargin() - document.left() - len1) / 2.0F + 23F, document.top() - 25, 0);
// 2.写入页脚
// 2.写入 第 X 页
String foot = "第 " + pageS + " 页";
Phrase footer = new Phrase(foot, fontDetail);
// 3.计算前半部分的foot1的长度,后面好定位最后一部分的'Y页'这俩字的x轴坐标,字体长度也要计算进去 = len
float len = bf.getWidthPoint(foot, presentFontSize);
// 4.拿到当前的PdfContentByte
PdfContentByte cb = writer.getDirectContent();
// 5.写入页脚1,x轴就是(右margin+左margin + right() -left()- len)/2.0F
// 再给偏移20F适合人类视觉感受,否则肉眼看上去就太偏左了
// ,y轴就是底边界-20,否则就贴边重叠到数据体里了就不是页脚了;注意Y轴是从下往上累加的,最上方的Top值是大于Bottom好几百开外的。
ColumnText.showTextAligned(cb,
Element.ALIGN_CENTER,
footer,
(document.rightMargin() + document.right() + document.leftMargin() - document.left() - len) / 2.0F + 20F,
document.bottom() + 15, 0);
}
}
// 添加水印
public void addWatermark(PdfWriter writer, Document document) {
try {
// 水印图片
Image waterImg = null;
if (StringUtils.isNotBlank(waterImgPath)) {
InputStream inputStream = PDFUtil.returnInputStream(waterImgPath);
waterImg = Image.getInstance(IOUtils.toByteArray(inputStream));
}
PdfContentByte pdfContent = writer.getDirectContent();
// 开始写入水印
pdfContent.beginText();
// 设置水印透明度
PdfGState gs = new PdfGState();
// 水印透明度
gs.setFillOpacity(1f);
pdfContent.setGState(gs);
// 文字水印
pdfContent.setColorFill(BaseColor.LIGHT_GRAY);
// 字体大小
pdfContent.setFontAndSize(bf, presentFontSize);
// showTextAligned 方法的参数分别是(文字对齐方式,位置内容,输出水印X轴位置,Y轴位置,旋转角度)
pdfContent.showTextAligned(Element.ALIGN_LEFT, waterContent, document.right() - 170, document.bottom() + 15, 0);
int length = waterContent.length();
// 图片水印
if (waterImg != null) {
// 设置坐标 绝对位置 X Y
waterImg.setAbsolutePosition(document.right() - (180 + length), document.bottom() + 10);
// 设置旋转弧度
waterImg.setRotation(0);// 旋转 弧度
// 设置旋转角度
waterImg.setRotationDegrees(0);// 旋转 角度
// 设置等比缩放
waterImg.scaleAbsolute(20, 20);// 自定义大小
// 写入图片水印
pdfContent.addImage(waterImg);
}
// 结束写入水印
pdfContent.endText();
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
}
/**
* 关闭文档时
*/
public void onCloseDocument(PdfWriter writer, Document document) {
}
}
调用:
/**
* 字体文件相对路径
*/
@Value("${pdfExport.font}")
private String pdfFont;
/**
* 导出PDF模板文件相对路径
*/
@Value("${pdfExport.pdfTemplate}")
private String pdfTemplate;
/**
* 水印图片路径
*/
@Value("${pdfExport.waterImg}")
private String waterImg;
public void test(){
byte[] pdfBytes = PDFUtil.createPDF(htmlToPdfHtml(logHtml), pdfFont, waterImg, "我是水印");
}
// 将html转为pdf模板
private String htmlToPdfHtml(String html) {
// 将html格式转为xhtml,自动添加闭合
Document doc = Jsoup.parse(html);
doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml).escapeMode(Entities.EscapeMode.xhtml);
html = doc.body().html();
// 过滤换行和 html 空格 ( 非标准 html)
html = fixSpace(html);
// 过滤特殊 unicode 字符
html = stripNonValidXMLCharacters(html);
Map dataMap = new HashMap<>();
dataMap.put("content", html);
// freemarker模板转HTML
return PDFUtil.freemarkerRender(dataMap, pdfTemplate);
}
// 过滤无效字符
private String stripNonValidXMLCharacters(String in) {
StringBuilder out = new StringBuilder(); // Used to hold the output.
char current; // Used to reference the current character.
if (in == null || ("".equals(in)))
return ""; // vacancy test.
for (int i = 0; i < in.length(); i++) {
current = in.charAt(i); // NOTE: No IndexOutOfBoundsException caught
// here; it should not happen.
if (current == 0x9 || current == 0xA || current == 0xD || current >= 0x20 && current <= 0xD7FF || current >= 0xE000 && current <= 0xFFFD)
out.append(current);
}
return out.toString();
}
// 过滤换行和 html 空格 ( 非标准 html)
private String fixSpace(String html) {
final String pattern = "&(\\s*)nbsp;|\\n";
return html.replaceAll(pattern, " ");
}
配置文件:
# pdf export config
pdfExport:
font: "static/fonts/simsun.ttc"
pdfTemplate: "templates/logModel/pdf_export_template.ftl"
waterImg: "static/images/watermark.png"