SpringBoot html转pdf 支持中文、图片水印+文字水印、页眉页脚 flying-saucer-pdf-itext5 + freemarker

使用 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"
 

你可能感兴趣的:(html2pdf,java,html,freemarker,itext)