SpringBoot + Itext7 html转pdf 解决分页,表格被分割留出空白,水印,页眉页脚页码。

Itext7 html转pdf 解决分页,表格被分割留出空白,水印,页眉页脚页码。

可以解决表格内容在当页展示不下时被挤到下一页留出很大的一片空白。

使用itext7的html2pdf插件。

引入jar包

        <!--html转pdf工具 itext7 html2pdf -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>html2pdf</artifactId>
            <version>3.0.5</version>
        </dependency>

工具类

import cn.cooptec.admin.core.utils.pdfmarker.PdfHeaderMarker;
import cn.cooptec.admin.core.utils.pdfmarker.PdfPageMarker;
import cn.cooptec.admin.core.utils.pdfmarker.PdfWaterMarker;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.attach.impl.OutlineHandler;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.AreaBreak;
import com.itextpdf.layout.element.IBlockElement;
import com.itextpdf.layout.element.IElement;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.layout.property.AreaBreakType;
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 java.io.*;
import java.util.List;
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<String, Object> 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) {
        byte[] result = null;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfWriter writer = new PdfWriter(outputStream, new WriterProperties().setFullCompressionMode(Boolean.TRUE));
        PdfDocument doc = new PdfDocument(writer);
        try {
            String waterText = "水印";
            if (StringUtils.isNotBlank(waterContent)) {
                String[] split = waterContent.split("-");
                waterText = split[0] + "-" + split[1];
            }
            doc.setDefaultPageSize(PageSize.A4);
            FontProvider fontProvider = new FontProvider();
            // 设置中文字体文件的路径,可以在classpath目录下
            fontProvider.addFont(fontFilePath, PdfEncodings.IDENTITY_H);

            // 获取字体,提供给水印 和 页码使用
            PdfFont pdfFont = fontProvider.getFontSet()
                    .getFonts()
                    .stream()
                    .findFirst()
                    .map(fontProvider::getPdfFont)
                    .orElse(null);
            // 添加页眉
            doc.addEventHandler(PdfDocumentEvent.START_PAGE, new PdfHeaderMarker(pdfFont, "页眉"));
            // 添加水印
            doc.addEventHandler(PdfDocumentEvent.INSERT_PAGE, new PdfWaterMarker(pdfFont, waterText, waterImgPath));
            // 添加页脚
            doc.addEventHandler(PdfDocumentEvent.END_PAGE, new PdfPageMarker(pdfFont));

            ConverterProperties properties = new ConverterProperties();
            fontProvider.addStandardPdfFonts();
            fontProvider.addSystemFonts();
            properties.setFontProvider(fontProvider);

            // PDF目录
            properties.setOutlineHandler(OutlineHandler.createStandardHandler());

            // 将html转为pdf代码块,按div生成每天一页pdf
            Document document = new Document(doc);
            List<IElement> iElements = HtmlConverter.convertToElements(htmlTmpStr, properties);
            int size = iElements.size();
            for (int i = 0; i < size; i++) {
                IElement iElement = iElements.get(i);
                document.add((IBlockElement) iElement);
                if (!(i == size - 1)) { // 不是最后一页
                    // 添加新的一页
                    document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
                }
            }
            document.close();
            result = outputStream.toByteArray();
        } finally {
            try {
                doc.close();
                writer.close();
                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();
    }
}

页眉

import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;

public class PdfHeaderMarker implements IEventHandler {

    private PdfFont pdfFont;
    private String title;

    public PdfHeaderMarker(PdfFont pdfFont, String title) {
        this.pdfFont = pdfFont;
        this.title = title;
    }

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        int pageNumber = pdf.getPageNumber(page);
        if (pageNumber != 1) { // 封面不展示页眉页脚
            Rectangle pageSize = page.getPageSize();
            PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
            Canvas canvas = new Canvas(pdfCanvas, pageSize);
            float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
            float y = pageSize.getTop() - 25;
            Paragraph p = new Paragraph(title)
                    .setFontSize(12)
                    .setFont(pdfFont);
            // 顶部中间位置
            canvas.showTextAligned(p, x, y, TextAlignment.CENTER);
            canvas.close();
        }
    }

}

页脚页码

import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;

public class PdfPageMarker implements IEventHandler {

    private PdfFont pdfFont;

    public PdfPageMarker(PdfFont pdfFont) {
        this.pdfFont = pdfFont;
    }

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        int pageNumber = pdf.getPageNumber(page);
        if (pageNumber != 1) { // 封面不展示页眉页脚
            pageNumber = pageNumber - 1;
            Rectangle pageSize = page.getPageSize();
            PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
            Canvas canvas = new Canvas(pdfCanvas, pageSize);
            float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
            float y = pageSize.getBottom() + 15;
            Paragraph p = new Paragraph("第" + pageNumber + "页")
                    .setFontSize(12)
                    .setFont(pdfFont);
            // 底部中间位置
            canvas.showTextAligned(p, x, y, TextAlignment.CENTER);
            canvas.close();
        }
    }

}

水印

import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.VerticalAlignment;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class PdfWaterMarker implements IEventHandler {

    private PdfFont pdfFont;
    private String waterContent;
    private String waterImgPath;

    public PdfWaterMarker(PdfFont pdfFont, String waterContent, String waterImgPath) {
        this.pdfFont = pdfFont;
        this.waterContent = waterContent;
        this.waterImgPath = waterImgPath;
    }

    @Override
    public void handleEvent(Event event) {

        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        Rectangle pageSize = page.getPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
        Canvas canvas = new Canvas(pdfCanvas, pageSize);
        Paragraph waterMarker = new Paragraph(waterContent)
                .setFont(pdfFont)
                .setOpacity(.5f)
                .setFontSize(13);
        // 右下角位置
        canvas.showTextAligned(waterMarker, pageSize.getRight() - 150, pageSize.getBottom() + 15, pdf.getNumberOfPages(), TextAlignment.LEFT, VerticalAlignment.BOTTOM, 0);

        // 水印图片
        Image waterImg = null;
        if (StringUtils.isNotBlank(waterImgPath)) {
            try {
                InputStream inputStream = returnInputStream(waterImgPath);
                ImageData waterImgData = ImageDataFactory.create(toByteArray(inputStream));
                waterImg = new Image(waterImgData);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 图片水印
        if (waterImg != null) {
            int length = waterContent.length();
            // 设置坐标 X Y
            waterImg.setFixedPosition(pdf.getNumberOfPages(), pageSize.getRight() - (168 + length), pageSize.getBottom() + 12);
            // 设置等比缩放
            waterImg.scaleAbsolute(20, 20);// 自定义大小
            // 写入图片水印
            canvas.add(waterImg);
        }

        canvas.close();
    }

    public static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }

    public static InputStream returnInputStream(String filePath) throws IOException {
        return new ClassPathResource(filePath).getInputStream();
    }

}

配置文件

# pdf export config
pdfExport:
  font: "static/fonts/simsun.ttc,0"
  pdfTemplate: "templates/logModel/pdf_export_template.ftl"
  waterImg: "static/images/watermark.png"

  • 4
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
下面是使用itext7导出pdf,添加分页页眉页脚Java代码示例: ```java import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfPage; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Paragraph; import com.itextpdf.layout.element.Text; import com.itextpdf.layout.renderer.DocumentRenderer; import com.itextpdf.layout.renderer.DrawContext; import com.itextpdf.layout.renderer.IRenderer; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; public class PdfGenerator { private static final String DEST = "output.pdf"; public static void main(String[] args) throws FileNotFoundException { File file = new File(DEST); file.getParentFile().mkdirs(); new PdfGenerator().createPdf(DEST); } public void createPdf(String dest) throws FileNotFoundException { PdfWriter writer = new PdfWriter(dest); PdfDocument pdf = new PdfDocument(writer); Document document = new Document(pdf); // 设置页眉页脚 pdf.addEventHandler(PdfDocumentEvent.START_PAGE, new PageEventHandler()); // 添加内容 List<String> paragraphs = new ArrayList<>(); paragraphs.add("Paragraph 1"); paragraphs.add("Paragraph 2"); paragraphs.add("Paragraph 3"); paragraphs.add("Paragraph 4"); paragraphs.add("Paragraph 5"); paragraphs.add("Paragraph 6"); paragraphs.add("Paragraph 7"); paragraphs.add("Paragraph 8"); paragraphs.add("Paragraph 9"); paragraphs.add("Paragraph 10"); for (String text : paragraphs) { document.add(new Paragraph(text)); } document.close(); } // 实现分页页眉页脚 private static class PageEventHandler implements IPdfPageEvent { @Override public void handleEvent(Event event) { PdfDocumentEvent docEvent = (PdfDocumentEvent) event; PdfDocument pdfDoc = docEvent.getDocument(); PdfPage page = docEvent.getPage(); int pageNumber = pdfDoc.getPageNumber(page); Rectangle pageSize = page.getPageSize(); PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdfDoc); Canvas canvas = new Canvas(pdfCanvas, pdfDoc, pageSize); // 添加页眉 canvas.showTextAligned(new Paragraph("Header").setFontColor(Color.GRAY), pageSize.getWidth() / 2, pageSize.getTop() - 30, TextAlignment.CENTER); // 添加页脚 canvas.showTextAligned(new Paragraph(String.format("Page %d", pageNumber)), pageSize.getWidth() / 2, pageSize.getBottom() + 30, TextAlignment.CENTER); canvas.close(); } } // 实现分页 private static class CustomRenderer extends DocumentRenderer { public CustomRenderer(Document document) { super(document); } @Override public void drawBackground(DrawContext drawContext) { super.drawBackground(drawContext); int pageNumber = getCurrentArea().getPageNumber(); if (pageNumber != 1) { drawContext.getCanvas().beginText().setFontAndSize(PdfFontFactory.createFont(), 12).moveText(36, 20).showText(String.valueOf(pageNumber)).endText(); } } @Override public IRenderer getNextRenderer() { return new CustomRenderer(getDocument()); } } } ``` 在上面的代码中,我们首先创建一个 `PdfWriter` 和 `PdfDocument`。然后我们创建一个 `Document` 并设置页眉页脚的代码。我们添加了一些段落到文档中。 我们使用了 `PageEventHandler` 实现了分页页眉页脚。在 `handleEvent` 方法中,我们获取了当前页码,并添加了页眉页脚。 我们还使用了 `CustomRenderer` 实现了分页。在 `drawBackground` 方法中,我们将当前页码添加到每一页的左下角。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值