【笑小枫的SpringBoot系列】【十六】SpringBoot生成PDF

news2024/9/22 17:26:10

关于笑小枫💕

Hello,我是笑小枫,欢迎来到我的世界,喜欢的朋友关注一下我呦,大伙的支持,就是我坚持写下去的动力。

笑小枫个人博客:https://www.xiaoxiaofeng.com

本文源码:https://github.com/hack-feng/maple-demo

本文简介

本文主要介绍了在SpringBoot项目下,通过代码和操作步骤,详细的介绍了如何操作PDF。希望可以帮助到准备通过JAVA操作PDF的你。

项目框架用的SpringBoot,但在JAVA中代码都是通用的。

本文涉及pdf操作,如下:

  • PDF模板制作
  • 基于PDF模板生成,并支持下载
  • 自定义中文字体
  • 完全基于代码生成,并保存到指定目录
  • 合并PDF,并保存到指定目录
  • 合并PDF,并支持下载

基于PDF模板生成:适用于固定格式的PDF模板,基于内容进行填空,例如:合同信息生成、固定格式表格等等

完全基于代码生成:适用于不固定的PDF,例如:动态表格、动态添加某块内容、不确定的内容大小等不确定的场景

PDF文件简介

PDF是可移植文档格式,是一种电子文件格式,具有许多其他电子文档格式无法相比的优点。PDF文件格式可以将文字、字型、格式、颜色及独立于设备和分辨率的图形图像等封装在一个文件中。该格式文件还可以包含超文本链接、声音和动态影像等电子信息,支持特长文件,集成度和安全可靠性都较高。在系统开发中通常用来生成比较正式的报告或者合同类的电子文档。

代码实现PDF操作

首先需要引入我们的依赖,这里通过maven管理依赖

在pom.xml文件添加以下依赖

<!--pdf操作-->
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.6</version>
</dependency>
<!-- 这个主要用来设置样式什么的 -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

基于PDF模板生成,并下载

PDF模板制作

  1. 首先在word或者其他软件里面制作模板,挑选你熟悉的软件即可,前提是可生成pdf。

image-20220815134838696

  1. 将word文件转为pdf文件。

image-20220815135121685

  1. 使用Adobe Acrobat软件操作pdf,这里用的是这个软件,只要能实现这个功能,其他的软件也可~

    选择表单编辑哈,我们要在对应的坑上添加表单占位

image-20220815135323496

  1. 在表单上添加文本域即可,所有的格式都用文本域即可,这里只是占坑。

    对应的域名称要与程序的名称对应,方便后面数据填充,不然后面需要手动处理赋值。

image-20220815135431811

image-20220815135520626

  1. 创建个简单的模板吧,要注意填充的空间要充足,不然会出现数据展示不全呦~

    效果如下:

image-20220815135622485

好了,到这里模板就生成好了,我们保存一下,然后放在我们的/resources/templates目录下

PDF生成代码编写

util包下创建PdfUtil.java工具类,代码如下:

package com.maple.demo.util;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;

import javax.servlet.ServletOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;


/**
 * @author 笑小枫
 * @date 2022/8/15
 * @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
 */
public class PdfUtil {

    private PdfUtil() {

    }

    /**
     * 利用模板生成pdf
     *
     * @param data         写入的数据
     * @param photoMap     图片信息
     * @param out          自定义保存pdf的文件流
     * @param templatePath pdf模板路径
     */
    public static void fillTemplate(Map<String, Object> data, Map<String, String> photoMap, ServletOutputStream out, String templatePath) {
        PdfReader reader;
        ByteArrayOutputStream bos;
        PdfStamper stamper;
        try {
            // 读取pdf模板
            reader = new PdfReader(templatePath);
            bos = new ByteArrayOutputStream();
            stamper = new PdfStamper(reader, bos);
            AcroFields acroFields = stamper.getAcroFields();
            // 赋值
            for (String name : acroFields.getFields().keySet()) {
                String value = data.get(name) != null ? data.get(name).toString() : null;
                acroFields.setField(name, value);
            }

            // 图片赋值
            for (Map.Entry<String, String> entry : photoMap.entrySet()) {
                if (Objects.isNull(entry.getKey())) {
                    continue;
                }
                String key = entry.getKey();
                String url = entry.getValue();
                // 根据地址读取需要放入pdf中的图片
                Image image = Image.getInstance(url);
                // 设置图片在哪一页
                PdfContentByte overContent = stamper.getOverContent(acroFields.getFieldPositions(key).get(0).page);
                // 获取模板中图片域的大小
                Rectangle signRect = acroFields.getFieldPositions(key).get(0).position;
                float x = signRect.getLeft();
                float y = signRect.getBottom();
                // 图片等比缩放
                image.scaleAbsolute(signRect.getWidth(), signRect.getHeight());
                // 图片位置
                image.setAbsolutePosition(x, y);
                // 在该页加入图片
                overContent.addImage(image);
            }

            // 如果为false那么生成的PDF文件还能编辑,一定要设为true
            stamper.setFormFlattening(true);
            stamper.close();
            Document doc = new Document();
            PdfCopy copy = new PdfCopy(doc, out);
            doc.open();
            PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
            copy.addPage(importPage);
            doc.close();
            bos.close();
        } catch (IOException | DocumentException e) {
            e.printStackTrace();
        }
    }
}

controller包下创建TestPdfController.java类,并i代码如下:

package com.maple.demo.controller;

import com.maple.demo.util.PdfUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 笑小枫
 * @date 2022/8/15
 * @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
 */
@Slf4j
@RestController
@RequestMapping("/example")
@Api(tags = "实例演示-PDF操作接口")
public class TestPdfController {

    @ApiOperation(value = "根据PDF模板导出PDF")
    @GetMapping("/exportPdf")
    public void exportPdf(HttpServletResponse response) {
        Map<String, Object> dataMap = new HashMap<>(16);
        dataMap.put("nickName", "笑小枫");
        dataMap.put("age", 18);
        dataMap.put("sex", "男");
        dataMap.put("csdnUrl", "https://zhangfz.blog.csdn.net/");
        dataMap.put("siteUrl", "https://www.xiaoxiaofeng.com/");
        dataMap.put("desc", "大家好,我是笑小枫。");

        Map<String, String> photoMap = new HashMap<>(16);
        photoMap.put("logo", "https://profile.csdnimg.cn/C/9/4/2_qq_34988304");

        // 设置response参数,可以打开下载页面
        response.reset();
        response.setCharacterEncoding("UTF-8");
        // 定义输出类型
        response.setContentType("application/PDF;charset=utf-8");
        // 设置名称
        response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
        try {
            ServletOutputStream out = response.getOutputStream();
            // 模板路径记
            PdfUtil.fillTemplate(dataMap, photoMap, out, "src/main/resources/templates/xiaoxiaofeng.pdf");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

重启项目,在浏览器访问:http://localhost:6666/example/exportPdf

导出的文件效果如下:👇

image-20220816165204811

完全基于代码生成,并保存

完全基于代码生成PDF文件,这个就比较定制化了,这里只讲常见的操作,大家可以用作参考:

自定义字体

PDF生成的时候,有没有遇到过丢失中文字体的问题呢,唉~解决一下

先下载字体,这里用黑体演示哈,下载地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345

下载完会得到一个Simhei.ttf文件,对我们就是要它

先在util包下创建一个字体工具类吧,代码如下:

注意:代码中相关的绝对路径要替换成自己的

package com.maple.demo.util;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPTable;

import java.io.IOException;
import java.util.List;

/**
 * @author 笑小枫
 * @date 2022/8/15
 * @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
 */
public class PdfFontUtil {

    private PdfFontUtil() {
    }

    /**
     * 基础配置,可以放相对路径,这里演示绝对路径,因为字体文件过大,这里不传到项目里面了,需要的自己下载
     * 下载地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
     */
    public static final String FONT = "D:\\font/Simhei.ttf";

    /**
     * 基础样式
     */
    public static final Font TITLE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 20, Font.BOLD);
    public static final Font NODE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 15, Font.BOLD);
    public static final Font BLOCK_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 13, Font.BOLD, BaseColor.BLACK);
    public static final Font INFO_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 12, Font.NORMAL, BaseColor.BLACK);
    public static final Font CONTENT_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);


    /**
     * 段落样式获取
     */
    public static Paragraph getParagraph(String content, Font font, Integer alignment) {
        Paragraph paragraph = new Paragraph(content, font);
        if (alignment != null && alignment >= 0) {
            paragraph.setAlignment(alignment);
        }
        return paragraph;
    }

    /**
     * 图片样式
     */
    public static Image getImage(String imgPath, float width, float height) throws IOException, BadElementException {
        Image image = Image.getInstance(imgPath);
        image.setAlignment(Image.MIDDLE);
        if (width > 0 && height > 0) {
            image.scaleAbsolute(width, height);
        }
        return image;
    }

    /**
     * 表格生成
     */
    public static PdfPTable getPdfTable(int numColumns, float totalWidth) {
        // 表格处理
        PdfPTable table = new PdfPTable(numColumns);
        // 设置表格宽度比例为%100
        table.setWidthPercentage(100);
        // 设置宽度:宽度平均
        table.setTotalWidth(totalWidth);
        // 锁住宽度
        table.setLockedWidth(true);
        // 设置表格上面空白宽度
        table.setSpacingBefore(10f);
        // 设置表格下面空白宽度
        table.setSpacingAfter(10f);
        // 设置表格默认为无边框
        table.getDefaultCell().setBorder(0);
        table.setPaddingTop(50);
        table.setSplitLate(false);
        return table;
    }

    /**
     * 表格内容带样式
     */
    public static void addTableCell(PdfPTable dataTable, Font font, List<String> cellList) {
        for (String content : cellList) {
            dataTable.addCell(getParagraph(content, font, -1));
        }
    }
}

pdf生成工具类

继续在util包下的PdfUtil.java工具类里添加,因为本文涉及到的操作比较多,这里只贴相关代码,最后统一贴一个完成的文件

注意:代码中相关的绝对路径要替换成自己的

	/**
     * 创建PDF,并保存到指定位置
     *
     * @param filePath 保存路径
     */
    public static void createPdfPage(String filePath) {
        // FileOutputStream 需要关闭,释放资源
        try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
            // 创建文档
            Document document = new Document();
            PdfWriter writer = PdfWriter.getInstance(document, outputStream);
            document.open();
            // 报告标题
            document.add(PdfFontUtil.getParagraph("笑小枫的网站介绍", TITLE_FONT, 1));
            document.add(PdfFontUtil.getParagraph("\n网站名称:笑小枫(www.xiaoxiaofeng.com)", INFO_FONT, -1));
            document.add(PdfFontUtil.getParagraph("\n生成时间:2022-07-02\n\n", INFO_FONT, -1));
            // 报告内容
            // 段落标题 + 报表图
            document.add(PdfFontUtil.getParagraph("文章数据统计", NODE_FONT, -1));
            document.add(PdfFontUtil.getParagraph("\n· 网站首页图\n\n", BLOCK_FONT, -1));
            // 设置图片宽高
            float documentWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
            float documentHeight = documentWidth / 580 * 320;
            document.add(PdfFontUtil.getImage("D:\\xiaoxiaofeng.jpg", documentWidth - 80, documentHeight - 80));
            // 数据表格
            document.add(PdfFontUtil.getParagraph("\n· 数据详情\n\n", BLOCK_FONT, -1));
            // 生成6列的表格
            PdfPTable dataTable = PdfFontUtil.getPdfTable(6, 500);
            // 设置表格
            List<String> tableHeadList = tableHead();
            List<List<String>> tableDataList = getTableData();
            PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableHeadList);
            for (List<String> tableData : tableDataList) {
                PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableData);
            }
            document.add(dataTable);
            document.add(PdfFontUtil.getParagraph("\n· 报表描述\n\n", BLOCK_FONT, -1));
            document.add(PdfFontUtil.getParagraph("数据报告可以监控每天的推广情况," +
                    "可以针对不同的数据表现进行分析,以提升推广效果。", CONTENT_FONT, -1));
            document.newPage();
            document.close();
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 模拟数据
     */
    private static List<String> tableHead() {
        List<String> tableHeadList = new ArrayList<>();
        tableHeadList.add("省份");
        tableHeadList.add("城市");
        tableHeadList.add("数量");
        tableHeadList.add("百分比1");
        tableHeadList.add("百分比2");
        tableHeadList.add("百分比3");
        return tableHeadList;
    }

    /**
     * 模拟数据
     */
    private static List<List<String>> getTableData() {
        List<List<String>> tableDataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            List<String> tableData = new ArrayList<>();
            tableData.add("浙江" + i);
            tableData.add("杭州" + i);
            tableData.add("276" + i);
            tableData.add("33.3%");
            tableData.add("34.3%");
            tableData.add("35.3%");
            tableDataList.add(tableData);
        }
        return tableDataList;
    }

controller包下的TestPdfController.java类中添加代码,因为本文涉及到的操作比较多,这里只贴相关代码,最后统一贴一个完成的文件,相关代码如下:

注意:代码中相关的绝对路径要替换成自己的

	@ApiOperation(value = "测试纯代码生成PDF到指定目录")
    @GetMapping("/createPdfLocal")
    public void create() {
        PdfUtil.createPdfPage("D:\\xxf.pdf");
    }

重启项目,在浏览器访问:http://localhost:6666/example/createPdfLocal

可以在D:\\test目录下看到xxf.pdf文件

image-20220816170702824

我们打开看一下效果:

image-20220816170841371

合并PDF,并保存

继续在util包下的PdfUtil.java工具类里添加,因为本文涉及到的操作比较多,这里只贴相关代码,最后统一贴一个完成的文件。

/**
     * 合并pdf文件
     *
     * @param files   要合并文件数组(绝对路径如{ "D:\\test\\1.pdf", "D:\\test\\2.pdf" , "D:\\test\\3.pdf"})
     * @param newFile 合并后存放的目录D:\\test\\xxf-merge.pdf
     * @return boolean 生成功返回true, 否則返回false
     */
    public static boolean mergePdfFiles(String[] files, String newFile) {
        boolean retValue = false;
        Document document;
        try (FileOutputStream fileOutputStream = new FileOutputStream(newFile)) {
            document = new Document(new PdfReader(files[0]).getPageSize(1));
            PdfCopy copy = new PdfCopy(document, fileOutputStream);
            document.open();
            for (String file : files) {
                PdfReader reader = new PdfReader(file);
                int n = reader.getNumberOfPages();
                for (int j = 1; j <= n; j++) {
                    document.newPage();
                    PdfImportedPage page = copy.getImportedPage(reader, j);
                    copy.addPage(page);
                }
            }
            retValue = true;
            document.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return retValue;
    }

controller包下的TestPdfController.java类中添加代码,因为本文涉及到的操作比较多,这里只贴相关代码,最后统一贴一个完成的文件,相关代码如下:

注意:代码中相关的绝对路径要替换成自己的

    @ApiOperation(value = "测试合并PDF到指定目录")
    @GetMapping("/mergePdf")
    public Boolean mergePdf() {
        String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
        String newFile = "D:\\test\\xxf-merge.pdf";
        return PdfUtil.mergePdfFiles(files, newFile);
    }

我们首先要准备两个文件D:\\test\\1.pdf,D:\\test\\2.pdf,这里可以指定文件,也可以是生成的pdf文件。

如果是处理生成的文件,这里说下思想:程序创建一个临时目录,注意要唯一命名,然后将生成PDF文件保存到这个目录,然后从这个目录下拿到pdf进行处理,最后处理完成,保存到对应的目录下,删除这个临时目录和下面的文件。这里不做演示。《合并PDF,并下载》里面略有涉及,但是处理的单个文件,稍微改造即可

重启项目,在浏览器访问:http://localhost:6666/example/mergePdf

可以在D:\\test目录下看到xxf-merge文件

image-20220816172150022

打开看一下效果:

image-20220816172236016

合并PDF,并下载

继续在util包下的PdfUtil.java工具类里添加,这里只贴将文件转为输出流,并删除文件的相关代码,合并相关代码见《合并PDF,并保存》的util类。


    /**
     * 读取PDF,读取后删除PDF,适用于生成后需要导出PDF,创建临时文件
     */
    public static void readDeletePdf(String fileName, ServletOutputStream outputStream) {
        File file = new File(fileName);
        if (!file.exists()) {
            System.out.println(fileName + "文件不存在");
        }
        try (InputStream in = new FileInputStream(fileName)) {
            IOUtils.copy(in, outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                Files.delete(file.toPath());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

controller包下的TestPdfController.java类中添加代码,因为本文涉及到的操作比较多,这里只贴相关代码,最后统一贴一个完成的文件,相关代码如下:

注意:代码中相关的绝对路径要替换成自己的

@ApiOperation(value = "测试合并PDF后并导出")
    @GetMapping("/exportMergePdf")
    public void createPdf(HttpServletResponse response) {
        // 设置response参数,可以打开下载页面
        response.reset();
        response.setCharacterEncoding("UTF-8");
        // 定义输出类型
        response.setContentType("application/PDF;charset=utf-8");
        // 设置名称
        response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
        try (ServletOutputStream out = response.getOutputStream()) {
            String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
            // 生成为临时文件,转换为流后,再删除该文件
            String newFile = "src\\main\\resources\\templates\\" + UUID.randomUUID() + ".pdf";
            boolean isOk = PdfUtil.mergePdfFiles(files, newFile);
            if (isOk) {
                PdfUtil.readDeletePdf(newFile, out);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

重启项目,在浏览器访问:http://localhost:6666/example/exportMergePdf会提示我们下载。

image-20220816172814426

完整代码

PdfUtil.java

package com.maple.demo.util;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import org.apache.commons.compress.utils.IOUtils;

import javax.servlet.ServletOutputStream;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.maple.demo.util.PdfFontUtil.*;

/**
 * @author 笑小枫
 * @date 2022/8/15
 * @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
 */
public class PdfUtil {

    private PdfUtil() {

    }

    /**
     * 利用模板生成pdf
     *
     * @param data         写入的数据
     * @param photoMap     图片信息
     * @param out          自定义保存pdf的文件流
     * @param templatePath pdf模板路径
     */
    public static void fillTemplate(Map<String, Object> data, Map<String, String> photoMap, ServletOutputStream out, String templatePath) {
        PdfReader reader;
        ByteArrayOutputStream bos;
        PdfStamper stamper;
        try {
            // 读取pdf模板
            reader = new PdfReader(templatePath);
            bos = new ByteArrayOutputStream();
            stamper = new PdfStamper(reader, bos);
            AcroFields acroFields = stamper.getAcroFields();
            // 赋值
            for (String name : acroFields.getFields().keySet()) {
                String value = data.get(name) != null ? data.get(name).toString() : null;
                acroFields.setField(name, value);
            }

            // 图片赋值
            for (Map.Entry<String, String> entry : photoMap.entrySet()) {
                if (Objects.isNull(entry.getKey())) {
                    continue;
                }
                String key = entry.getKey();
                String url = entry.getValue();
                // 根据地址读取需要放入pdf中的图片
                Image image = Image.getInstance(url);
                // 设置图片在哪一页
                PdfContentByte overContent = stamper.getOverContent(acroFields.getFieldPositions(key).get(0).page);
                // 获取模板中图片域的大小
                Rectangle signRect = acroFields.getFieldPositions(key).get(0).position;
                float x = signRect.getLeft();
                float y = signRect.getBottom();
                // 图片等比缩放
                image.scaleAbsolute(signRect.getWidth(), signRect.getHeight());
                // 图片位置
                image.setAbsolutePosition(x, y);
                // 在该页加入图片
                overContent.addImage(image);
            }

            // 如果为false那么生成的PDF文件还能编辑,一定要设为true
            stamper.setFormFlattening(true);
            stamper.close();
            Document doc = new Document();
            PdfCopy copy = new PdfCopy(doc, out);
            doc.open();
            PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
            copy.addPage(importPage);
            doc.close();
            bos.close();
        } catch (IOException | DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建PDF,并保存到指定位置
     *
     * @param filePath 保存路径
     */
    public static void createPdfPage(String filePath) {
        // FileOutputStream 需要关闭,释放资源
        try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
            // 创建文档
            Document document = new Document();
            PdfWriter writer = PdfWriter.getInstance(document, outputStream);
            document.open();
            // 报告标题
            document.add(PdfFontUtil.getParagraph("笑小枫的网站介绍", TITLE_FONT, 1));
            document.add(PdfFontUtil.getParagraph("\n网站名称:笑小枫(www.xiaoxiaofeng.com)", INFO_FONT, -1));
            document.add(PdfFontUtil.getParagraph("\n生成时间:2022-07-02\n\n", INFO_FONT, -1));
            // 报告内容
            // 段落标题 + 报表图
            document.add(PdfFontUtil.getParagraph("文章数据统计", NODE_FONT, -1));
            document.add(PdfFontUtil.getParagraph("\n· 网站首页图\n\n", BLOCK_FONT, -1));
            // 设置图片宽高
            float documentWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
            float documentHeight = documentWidth / 580 * 320;
            document.add(PdfFontUtil.getImage("D:\\xiaoxiaofeng.jpg", documentWidth - 80, documentHeight - 80));
            // 数据表格
            document.add(PdfFontUtil.getParagraph("\n· 数据详情\n\n", BLOCK_FONT, -1));
            // 生成6列的表格
            PdfPTable dataTable = PdfFontUtil.getPdfTable(6, 500);
            // 设置表格
            List<String> tableHeadList = tableHead();
            List<List<String>> tableDataList = getTableData();
            PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableHeadList);
            for (List<String> tableData : tableDataList) {
                PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableData);
            }
            document.add(dataTable);
            document.add(PdfFontUtil.getParagraph("\n· 报表描述\n\n", BLOCK_FONT, -1));
            document.add(PdfFontUtil.getParagraph("数据报告可以监控每天的推广情况," +
                    "可以针对不同的数据表现进行分析,以提升推广效果。", CONTENT_FONT, -1));
            document.newPage();
            document.close();
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 模拟数据
     */
    private static List<String> tableHead() {
        List<String> tableHeadList = new ArrayList<>();
        tableHeadList.add("省份");
        tableHeadList.add("城市");
        tableHeadList.add("数量");
        tableHeadList.add("百分比1");
        tableHeadList.add("百分比2");
        tableHeadList.add("百分比3");
        return tableHeadList;
    }

    /**
     * 模拟数据
     */
    private static List<List<String>> getTableData() {
        List<List<String>> tableDataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            List<String> tableData = new ArrayList<>();
            tableData.add("浙江" + i);
            tableData.add("杭州" + i);
            tableData.add("276" + i);
            tableData.add("33.3%");
            tableData.add("34.3%");
            tableData.add("35.3%");
            tableDataList.add(tableData);
        }
        return tableDataList;
    }

    /**
     * 合并pdf文件
     *
     * @param files   要合并文件数组(绝对路径如{ "D:\\test\\1.pdf", "D:\\test\\2.pdf" , "D:\\test\\3.pdf"})
     * @param newFile 合并后存放的目录D:\\test\\xxf-merge.pdf
     * @return boolean 生成功返回true, 否則返回false
     */
    public static boolean mergePdfFiles(String[] files, String newFile) {
        boolean retValue = false;
        Document document;
        try (FileOutputStream fileOutputStream = new FileOutputStream(newFile)) {
            document = new Document(new PdfReader(files[0]).getPageSize(1));
            PdfCopy copy = new PdfCopy(document, fileOutputStream);
            document.open();
            for (String file : files) {
                PdfReader reader = new PdfReader(file);
                int n = reader.getNumberOfPages();
                for (int j = 1; j <= n; j++) {
                    document.newPage();
                    PdfImportedPage page = copy.getImportedPage(reader, j);
                    copy.addPage(page);
                }
            }
            retValue = true;
            document.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 读取PDF,读取后删除PDF,适用于生成后需要导出PDF,创建临时文件
     */
    public static void readDeletePdf(String fileName, ServletOutputStream outputStream) {
        File file = new File(fileName);
        if (!file.exists()) {
            System.out.println(fileName + "文件不存在");
        }
        try (InputStream in = new FileInputStream(fileName)) {
            IOUtils.copy(in, outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                Files.delete(file.toPath());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

PdfFontUtil.java

package com.maple.demo.util;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPTable;

import java.io.IOException;
import java.util.List;

/**
 * @author 笑小枫
 * @date 2022/8/15
 * @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
 */
public class PdfFontUtil {

    private PdfFontUtil() {
    }

    /**
     * 基础配置,可以放相对路径,这里演示绝对路径,因为字体文件过大,这里不传到项目里面了,需要的自己下载
     * 下载地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
     */
    public static final String FONT = "D:\\font/Simhei.ttf";

    /**
     * 基础样式
     */
    public static final Font TITLE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 20, Font.BOLD);
    public static final Font NODE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 15, Font.BOLD);
    public static final Font BLOCK_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 13, Font.BOLD, BaseColor.BLACK);
    public static final Font INFO_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 12, Font.NORMAL, BaseColor.BLACK);
    public static final Font CONTENT_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);


    /**
     * 段落样式获取
     */
    public static Paragraph getParagraph(String content, Font font, Integer alignment) {
        Paragraph paragraph = new Paragraph(content, font);
        if (alignment != null && alignment >= 0) {
            paragraph.setAlignment(alignment);
        }
        return paragraph;
    }

    /**
     * 图片样式
     */
    public static Image getImage(String imgPath, float width, float height) throws IOException, BadElementException {
        Image image = Image.getInstance(imgPath);
        image.setAlignment(Image.MIDDLE);
        if (width > 0 && height > 0) {
            image.scaleAbsolute(width, height);
        }
        return image;
    }

    /**
     * 表格生成
     */
    public static PdfPTable getPdfTable(int numColumns, float totalWidth) {
        // 表格处理
        PdfPTable table = new PdfPTable(numColumns);
        // 设置表格宽度比例为%100
        table.setWidthPercentage(100);
        // 设置宽度:宽度平均
        table.setTotalWidth(totalWidth);
        // 锁住宽度
        table.setLockedWidth(true);
        // 设置表格上面空白宽度
        table.setSpacingBefore(10f);
        // 设置表格下面空白宽度
        table.setSpacingAfter(10f);
        // 设置表格默认为无边框
        table.getDefaultCell().setBorder(0);
        table.setPaddingTop(50);
        table.setSplitLate(false);
        return table;
    }

    /**
     * 表格内容带样式
     */
    public static void addTableCell(PdfPTable dataTable, Font font, List<String> cellList) {
        for (String content : cellList) {
            dataTable.addCell(getParagraph(content, font, -1));
        }
    }
}

TestPdfController.java

package com.maple.demo.controller;

import com.maple.demo.util.PdfUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author 笑小枫
 * @date 2022/8/15
 * @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a>
 */
@Slf4j
@RestController
@RequestMapping("/example")
@Api(tags = "实例演示-PDF操作接口")
public class TestPdfController {

    @ApiOperation(value = "根据PDF模板导出PDF")
    @GetMapping("/exportPdf")
    public void exportPdf(HttpServletResponse response) {
        Map<String, Object> dataMap = new HashMap<>(16);
        dataMap.put("nickName", "笑小枫");
        dataMap.put("age", 18);
        dataMap.put("sex", "男");
        dataMap.put("csdnUrl", "https://zhangfz.blog.csdn.net/");
        dataMap.put("siteUrl", "https://www.xiaoxiaofeng.com/");
        dataMap.put("desc", "大家好,我是笑小枫。");

        Map<String, String> photoMap = new HashMap<>(16);
        photoMap.put("logo", "https://profile.csdnimg.cn/C/9/4/2_qq_34988304");

        // 设置response参数,可以打开下载页面
        response.reset();
        response.setCharacterEncoding("UTF-8");
        // 定义输出类型
        response.setContentType("application/PDF;charset=utf-8");
        // 设置名称
        response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
        try {
            ServletOutputStream out = response.getOutputStream();
            // 模板路径记
            PdfUtil.fillTemplate(dataMap, photoMap, out, "src/main/resources/templates/xiaoxiaofeng.pdf");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @ApiOperation(value = "测试纯代码生成PDF到指定目录")
    @GetMapping("/createPdfLocal")
    public void create() {
        PdfUtil.createPdfPage("D:\\test\\xxf.pdf");
    }

    @ApiOperation(value = "测试合并PDF到指定目录")
    @GetMapping("/mergePdf")
    public Boolean mergePdf() {
        String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
        String newFile = "D:\\test\\xxf-merge.pdf";
        return PdfUtil.mergePdfFiles(files, newFile);
    }

    @ApiOperation(value = "测试合并PDF后并导出")
    @GetMapping("/exportMergePdf")
    public void createPdf(HttpServletResponse response) {
        // 设置response参数,可以打开下载页面
        response.reset();
        response.setCharacterEncoding("UTF-8");
        // 定义输出类型
        response.setContentType("application/PDF;charset=utf-8");
        // 设置名称
        response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
        try (ServletOutputStream out = response.getOutputStream()) {
            String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
            // 生成为临时文件,转换为流后,再删除该文件
            String newFile = "src\\main\\resources\\templates\\" + UUID.randomUUID() + ".pdf";
            boolean isOk = PdfUtil.mergePdfFiles(files, newFile);
            if (isOk) {
                PdfUtil.readDeletePdf(newFile, out);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12707.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DVWA之SQL注入(盲注)

文章目录方法一&#xff1a;基于布尔盲注1.判断是否存在注入&#xff0c;注入类型2.猜数据库名3.猜解数据库中的表名4.猜列名5.猜表中的字段值方法二&#xff1a;基于时间盲注1.判断是否存在注入&#xff0c;注入是字符型还是数字型2.猜解当前数据库名3.猜解数据库中的表名4.猜…

ASPNetZero 11.4 Release Angular + MVC + Crack

Asp. NET 带有现代UI和稳定的结构&#xff0c;是新的网页应用的起点。它会提供预建和工作页以及很强的基本构造。 基础解决方案 你的下一个网络应用程序 ASP.NET Zero 是具有现代 UI 和 SOLID 架构以及完整源代码的新 Web 应用程序的起点。 ASP.NET Zero的好处 ASP.NET Zero 通…

化合物应用-动物给药方式

给药方式 针对实验动物的给药方式有很多种&#xff0c;总体可分为局部给药和系统给药。系统给药又可分为肠外给药和肠内给药。 1.1 肠内给药&#xff08;Enteral administration&#xff09; 口服&#xff08;per os&#xff0c;p.o&#xff09;给药由于具有经济、方便、安全…

虚拟形象sdk哪个好?可以快速制作专属元宇宙形象

元宇宙的火爆&#xff0c;催生了很多提供元宇宙基座的服务商&#xff0c;目前市面上提供虚拟形象SDK的服务商不少于20家&#xff0c;这里小编给大家推荐ZEGO即构科技的Avatar虚拟形象SDK。 即构Avatar虚拟形象SDK概述 即构Avatar支持自定义管理人物的虚拟形象&#xff0c;通过默…

【虹科新品】 HK-MR430330绝对式光纤编码器(上)

虹科MR430&330系列绝对式光纤编码器是用于确定轴位置的角度计&#xff0c;均为纯光学无源设计。HK-MR330系列适用于间隔距离较大的编码器和控制器&#xff0c;HK-MR430 系列适用于由于体积小而提供小空间的系统。与增量式旋转编码器相比&#xff0c;测量值在开启后立即可用…

【JAVA程序设计】基于SSM的图书管理系统-有论文文档

基于SSM的图书管理系统-有文档项目获取项目简介开发环境项目技术功能结构文档目录运行截图项目获取 获取方式&#xff08;点击下载&#xff09;&#xff1a;是云猿实战 项目经过多人测试运行&#xff0c;可以确保100%成功运行。 项目简介 这是一个基于ssm的图书管理系统&…

canvas绘制时钟

这篇文章用于介绍html5的新标签&#xff0c;<canvas></canvas> Canvas介绍 canvas是HTML5新增的元素&#xff0c;通过javascript脚本绘制图形。那么canvas可以用来干啥呢&#xff1f; 制作web网页游戏数据可视化&#xff1b;即&#xff1a;echarts就是基于canva…

ajax 学习记录

ajax 学习记录ecplise 下载安装创建项目创建ajax后台请求处理类新建包创建servletAjaxServlet 内容web.xml新建index.htmlindex.html内容运行ecplise 下载安装 ecplise 21.06 Eclipse IDE for Enterprise Java and Web Developers 下载 下载完直接解压运行 创建项目 先下载…

基于最小二乘支持向量机(LS-SVM)进行分类、函数估计、时间序列预测和无监督学习附Matlab代码

​✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法…

线性回归(机器学习)

用神经网络的思维来看待线性回归 单层的神经网络&#xff0c;其实就是一个神经元&#xff0c;可以完成一些线性的工作&#xff0c;比如拟合一条直线&#xff0c;这用一个神经元就可以实现。 当变量多于一个时&#xff0c;两个变量的量和数值有可能差别很大&#xff0c;这种情况…

MicroApp的Vite项目,特别全面

目录 前言 简介 分离 修改基座 子应用修改 修改vite.config.js​编辑 修改index.html 基座修改 处理子应用静态资源 效果 基座与子应用传值 在基座的MicroApp的index页面修改 在子应用的App.tsx文件进行useEffect的监听 效果​编辑 去掉菜单栏头部 效果​编辑 …

SpringBoot面试

0. Spring Boot 原理 Spring Boot 是由 Pivotal 团队提供的全新框架&#xff0c;其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。通过这种方式&#xff0c;Spring Boot 致力…

D. Bandit in a City(DFS + 叶子节点数目)

Problem - 1436D - Codeforces 输出标准输出 城市里出现了强盗! 他们中的一个正试图尽可能多地抓捕市民。 这个城市由n个广场组成&#xff0c;由n-1条道路连接&#xff0c;从任何其他广场都可以到达任何广场。1号广场是主广场。 星期天散步后&#xff0c;所有的道路都改为单行…

uni-app —— 小程序登录功能的相关实现

文章目录 前言一、示例图二、静态页面的搭建 1.登录页面的静态页面css样式设置2.我的页面的静态页面css样式设置三、登录页面的实现逻辑 1.进行表单验证的组件2.验证登录成功的思路3.具体代码实现一、示例图 二、静态页面的搭建 实现思路&#xff1a; 主要需要实现的功能点&am…

企业为什么做不好生产计划?

生产计划是企业对生产任务作出统筹安排&#xff0c;具体拟定生产产品的品种、数量、质量和进度的计划。是企业经营计划的重要组成部分&#xff0c;是企业进行生产管理的重要依据。既是实现企业经营目标的重要手段&#xff0c;也是组织和指导企业生产活动有计划进行的依据。企业…

【微服务】SpringCloud微服务注册源码解析

目录 一、前言 1、简述 2、SpringCloudCommons 项目 二、客户端服务注册 1、流程图 2、入口 2.1、客户端注册引入依赖 3、EurekaServiceRegistry服务注册机 3.1、EurekaServiceRegistry注册逻辑 4、ApplicationInfoManager 4.1、setInstanceStatus(InstanceStatus s…

【区块链技术与应用】(七)

资料来源 https://pkg.go.dev/github.com/hyperledger/fabric-sdk-go#section-readme https://github.com/hyperledger/fabric-sdk-go https://wiki.hyperledger.org/display/fabric https://github.com/hyperledger/fabric-samples 书接上回&#xff0c;补充getway链码分析 上…

maven assembly打包生成Java应用启动脚本bat和sh

1. maven插件介绍 springboot应用通过maven插件appassembler-maven-plugi生成启动脚本bat和sh。根据官网介绍&#xff0c;这个插件主要用于生成启动 java应用程序的脚本&#xff0c;能将项目依赖jar能够打包目录中&#xff0c;并且它们加入启动脚本类路径中。 主要命令 appas…

springboot瑞吉外卖

创建数据库,项目初始化静态资源不在static目录下&#xff0c;如何映射结果类登录过滤器拦截路径全局异常处理器分页查询消息转换器修改禁用分页编辑公共字段自动填充使用ThreadLocal新增用户绑定的数据不可删除上传下载前端传递的数据&#xff0c;不在同一张表时&#xff0c;DT…

java设计模式之策略模式

一&#xff1a;策略模式 1.什么是策略模式? 模板方法模式是一种行为设计模式&#xff0c; 它在超类中定义了一个算法的框架&#xff0c; 允许子类在不修改结构的情况下重写算法的特定步骤。 策略模式的基本介绍 1.策略模式&#xff08;Strategy Pattern&#xff09;中&#x…