依赖包

<!-- pdf 开始 -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.6</version>
        </dependency>

        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-core</artifactId>
            <version>9.0.3</version>
        </dependency>

        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf-itext5</artifactId>
            <version>9.0.3</version>
        </dependency>
        <!-- pdf 结束 -->
        <!-- freemarker 开始 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <!-- freemarker 结束 -->

工具类

import com.itextpdf.text.pdf.BaseFont;
import com.example.call.file.FileService;
import com.example.call.file.vo.FileUploadVo;
import com.example.call.quote.vo.QResultOrderCopiesVO;
import com.example.core.model.ReturnT;
import com.example.core.util.LocalDateTimeUtil;
import com.example.core.util.MultipartFileUtil;
import com.example.core.util.PropertyUtil;
import com.example.core.util.SpringUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.multipart.MultipartFile;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * <生成PDF文件>
 */
public class PdfTool1 {

    /**
     * <生成合并报价单PDF文件,并上传OSS返回其URL>
     * @param vo 报价单数据对象
     * @return java.lang.String
     * @author hmy
     * @date 2022/12/28 21:00
     * @version 1.0.0
     */
    public static String createPdfFile(QtResultOrderCopiesVO vo){
        try {
            String htmlString = "";
            try {
                Configuration cfg = SpringUtil.getBean(Configuration.class);
                //本次测试不依赖Spring容器时使用下边的
//            Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
//            cfg.setDirectoryForTemplateLoading(new File("D:\\myfiles\\xianniu-quote\\src\\main\\resources\\templates"));
//            cfg.setDefaultEncoding("UTF-8");
//            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

                Template temp = cfg.getTemplate("t_merge.ftl");
                htmlString = FreeMarkerTemplateUtils.processTemplateIntoString(temp, vo);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("PDF模板获取异常");
            }
            byte[] dataArray = html2pdf(htmlString);
            String url = uploadPdf(dataArray);
            return url;
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("生成PDF异常");
        }
    }

    private static String uploadPdf(byte[] dataArray) throws IOException {
        String fileName = "生成结果"+ LocalDateTimeUtil.getCurrentStrByFormatter(LocalDateTimeUtil.yyyyMMddHHmmssSSS) +".pdf";
        PropertyUtil p = SpringUtil.getBean(PropertyUtil.class);
        String fileUploadPath = p.getProperties("xxx.file-upload-path"); //临时文件保存目录
        String filePath = ExcelUtils.getWebRootPath(fileUploadPath+ fileName);
        FileOutputStream fileOutputStream = new FileOutputStream(filePath);
        // 输出pdf
        assert dataArray != null;
        fileOutputStream.write(dataArray);
        fileOutputStream.flush();
        fileOutputStream.close();

        File file = new File(filePath);
        FileService fileService = SpringUtil.getBean(FileService.class);
        String url = "";
        MultipartFile multipartFile = MultipartFileUtil.toMultipartFile(file);
        //将导入结果反馈给前端
        ReturnT<FileUploadVo> r = fileService.uploadFile(multipartFile);
        if(r.verifySuccess()) {
            url = r.getData().getUrl();
        }
        return url;
    }

    /**
     * html转pdf
     * @param htmlString html字符
     * @return byte[]
     */
    public static byte[] html2pdf(String htmlString) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocumentFromString(htmlString);
            // 解决中文支持问题
            ITextFontResolver fontResolver = renderer.getFontResolver();
            fontResolver.addFont("simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            fontResolver.addFont("fontawesome-webfont.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            //解决图片的相对路径问题,图片使用base64编码即可,否则就得如下使用,建议使用base64
//	        renderer.getSharedContext().setBaseURL("http://www.baidu.com/test.jpg");
            renderer.getSharedContext().setReplacedElementFactory(new B64ImgReplacedElementFactory());
            renderer.layout();
            renderer.createPDF(byteArrayOutputStream);
            return byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                byteArrayOutputStream.flush();
                byteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }


    /**
     * 生成pdf单元测试
     */
//    @Test
//    public void createMergeResultPdfTest() {
//        QResultOrderCopiesVO vo = new QResultOrderCopiesVO();
//        Map<String, Object> model = BeanUtil.beanToMap(vo);
//        // other value map
//
//        try {
//            Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
////            cfg.setDirectoryForTemplateLoading(new File("D:\\myfiles\\xia-quote\\src\\main\\resources\\templates"));
//            cfg.setDirectoryForTemplateLoading(new File("src\\main\\resources\\templates"));
//            cfg.setDefaultEncoding("UTF-8");
//            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
//
//            Template temp = cfg.getTemplate("t_merge" + ".ftl");
//
//            String htmlString = FreeMarkerTemplateUtils.processTemplateIntoString(temp, model);
//            FileUtil.writeUtf8String(htmlString, "createMergeResultPdfTest.html");
//            byte[] dataArray = PdfTool.html2pdf(htmlString);
//            if (dataArray != null && dataArray.length > 0) {
//                // 输出pdf
//                FileOutputStream fileOutputStream = new FileOutputStream("createMergeResultOrderPdfTest.pdf");
//                fileOutputStream.write(dataArray);
//                fileOutputStream.flush();
//                fileOutputStream.close();
//            }
//        } catch (Exception e) {
//            e.printStackTrace();
//            throw new RuntimeException("PDF模板获取异常");
//        }
//    }

}

辅助类

// 支持base64的实现
public class B64ImgReplacedElementFactory implements ReplacedElementFactory {
    public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac, int cssWidth, int cssHeight) {
        Element e = box.getElement();
        if (e == null) {
            return null;
        }
        String nodeName = e.getNodeName();
        if (nodeName.equals("img")) {
            String attribute = e.getAttribute("src");
            FSImage fsImage;
            try {
                fsImage = buildImage(attribute, uac);
            } catch (BadElementException e1) {
                fsImage = null;
            } catch (IOException e1) {
                fsImage = null;
            }
            if (fsImage != null) {
                if (cssWidth != -1 || cssHeight != -1) {
                    fsImage.scale(cssWidth, cssHeight);
                }
                return new ITextImageElement(fsImage);
            }
        }
        return null;
    }

    public void remove(Element arg0) {
        // Auto-generated method stub

    }

    public void reset() {
        // Auto-generated method stub

    }

    public void setFormSubmissionListener(FormSubmissionListener arg0) {
        // Auto-generated method stub

    }

    protected FSImage buildImage(String srcAttr, UserAgentCallback uac) throws IOException, BadElementException {
        FSImage fsImage;
        if (srcAttr.startsWith("data:image/")) {
            String b64encoded = srcAttr.substring(srcAttr.indexOf("base64,") + "base64,".length(), srcAttr.length());
            byte[] decodedBytes = Base64.getDecoder().decode(b64encoded);
            fsImage = new ITextFSImage(Image.getInstance(decodedBytes));
        } else {
            fsImage = uac.getImageResource(srcAttr).getImage();
        }
        return fsImage;
    }
}