使用freemarker+itextpdf通过HTML模版导出PDF

本文介绍了一种通过模版导出PDF的方法,利用Freemarker渲染Word模版,并使用itextpdf进行导出。文章详细记录了实现过程中的关键步骤及解决的问题,包括中文显示、图片处理、水印和分页等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

支持作者

最便宜的卫生纸

一 简介

     因公司业务需求需要做一个通过模版导出PDF的功能,模版是word文档,模版中包含图片,文字,水印,分页。遇到这种没做过的功能,没什么可说的,直接问度娘。结果度娘给不了我想要的,只好去github上下了一些相关的项目,通过筛选和实验终于找到了不错的代码,但是在使用过程中也是踩了无数坑,随记录下来。

二 思路

   通过模版导出PDF的方法有很多,但是思路都大致相同,总结起来就是 渲染模版 2 导出模版。以为之前使用过freemarker,所以我这里渲染模版的技术用的是freemarker,然后在使用itextpdf将渲染好的模版导出。

三 踩过的坑

1 Win下中文不显示 2 图片不显示 3 Linux下中文不显示 4 图片样式跑偏 5 分页加水印

四 上代码

4.1 准备工作

4.11首先准备好一个word模版,如下图所示

  

4.1.2 将word模版通过wps或其他工具另存为  转为HTML模版.

转为html后 ${}会被 分开,通过在线格式化工具排版HTML,将${}之间多余的代码删掉,重新恢复到${},批量操作即可.

4.1.3 新建一个ftl文件,将HTML代码拷贝进去 如下图所示

4.1.4 maven坐标

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.23</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.4.1</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.0.3</version>
        </dependency>

4.2 写代码 新建一个PDF工具类

4.2.1 第一步 渲染模版 代码如下

      //获取模板并填充数据
      public static String getContent(String fileName,Object data) throws Exception{
           // 创建一个Configuration对象
            Configuration configuration = new Configuration(Configuration.getVersion());
            Configuration config = new Configuration(Configuration.getVersion());//FreeMarker配置
           // 告诉config对象模板文件存放的路径。
           configuration.setDirectoryForTemplateLoading(new File(PdfUtil.class.getClassLoader().getResource("templates/").getPath()));
          // 设置config的默认字符集。一般是utf-8
          configuration.setDefaultEncoding("utf-8");
          //从config对象中获得模板对象。需要制定一个模板文件的名字。
          Template template = configuration.getTemplate(fileName+".ftl");
          StringWriter writer = new StringWriter();
          //模版和数据匹配
          template.process(data, writer);
          writer.flush();
          String html = writer.toString();
          return getImgs(html);
      }

4.2.2 第二步 替换模版中的图片路径 代码如下

    //替换html中的图片路径
    private static String getImgs(String content) {
        String img = "";
        Pattern p_image;
        Matcher m_image;
        String str = "";
        String[] images = null;
        String regEx_img = "(<img.*src\\s*=\\s*(.*?)[^>]*?>)";
        p_image = Pattern.compile(regEx_img, Pattern.CASE_INSENSITIVE);
        m_image = p_image.matcher(content);
        if(m_image!=null){
            while (m_image.find()) {
                img = m_image.group();
                Matcher m = Pattern.compile("src\\s*=\\s*\"?(.*?)(\"|>|\\s+)")
                        .matcher(img);
                if(m!=null){
                    while (m.find()) {
                        String tempSelected = m.group(1);
                        str = tempSelected;
                        String classpath= PathUtil.getRootPath();
                        String ulr=classpath.substring(5,classpath.length());
                        content=content.replace(str,ulr+"templates/"+str);
                    }
                }
            }
        }
       return content;
    }

工具类 PathUtil

/**
 * @author:change
 * @description:获取绝对路径
 * @date:2018/6/28 0028
 */
public class PathUtil {
   static String path1;
    static String download;

    public static String getRootPath() {
        String line = File.separator;
        String path = Thread.currentThread().getContextClassLoader().getResource("").toString();
        //windows下
        if ("\\".equals(line)) {
/*            path = path.replace("/", "\\");  // 将/换成\\*/
            path1 = path;
        }
        //linux下
        if ("/".equals(line)) {
            path = path.replace("\\", "/");
            path1 = path;
        }
        return path1;
    }

    public static void main(String[] args) {
        System.out.println(getRootPath());
        System.out.println(PdfUtil.class.getClassLoader().getResource("").getPath());
    }
}

4.2.3 设置pdf字符集 代码如下

 /**
     * 设置字符集
     */
    public static class MyFontsProvider extends XMLWorkerFontProvider {


        public MyFontsProvider(){
            super(null, null);
        }

        @Override
        public Font getFont(final String fontname, String encoding, float size, final int style) {
            String fntnames = fontname;
            Font FontChinese = null;
            if (fntnames == null) {
                fntnames = "宋体";
            }
            if (size == 0) {
                size = 4;
            }
            try{
                BaseFont bfChinese = BaseFont.createFont("STSong-Light",
                        "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                FontChinese = new Font(bfChinese, 12, Font.NORMAL);
            }catch (Exception e){
                e.printStackTrace();
            }
            if(FontChinese==null){
                FontChinese = super.getFont(fntnames, encoding, size, style);
            }
            return FontChinese;
        }
    }

4.2.4 生产PDF并下载 代码如下

    /**
     * 生成PDF到输出流中(ServletOutputStream用于下载PDF)
     * @param templateName ftl模板名称
     * @param data 输入到FTL中的数据
     * @param response HttpServletResponse
     * @return
     */
    public static OutputStream exportToResponse(String templateName, Object data,
                                         HttpServletResponse response)throws Exception{
        TEMPLATENAMES=templateName;
        String html= getContent(templateName,data);
        response.reset();
        response.setContentType("application/pdf;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename="+ new String((templateName + ".pdf").getBytes(), "iso-8859-1"));
        try{
            OutputStream out = null;
            ITextRenderer render = null;
            out = response.getOutputStream();
            //设置文档大小
            Document document = new Document(PageSize.A4);
            PdfWriter writer = PdfWriter.getInstance(document, out);
            writer.setStrictImageSequence(true);
            //输出为PDF文件
            convertToPDF(writer,document,html);
            return out;
        }catch (Exception ex){
            System.out.println(ex);
            throw  new SimpleException("PDF export to response fail");
        }

    }

    /**
     * @description PDF文件生成
     */
    private  static void convertToPDF(PdfWriter writer,Document document,String htmlString)throws Exception{
        document.open();
        MyFontsProvider fontProvider = new MyFontsProvider();
        fontProvider.addFontSubstitute("lowagie", "garamond");
        fontProvider.setUseUnicode(true);
        CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
        HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);
        htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
        XMLWorkerHelper.getInstance().getDefaultCssResolver(true);
         if(TEMPLATENAMES.equals("合同")||TEMPLATENAMES.equals("合同模版"))
         writer.setPageEvent(new BackGroundImage());
         if(TEMPLATENAMES.equals("设计合同"))
         writer.setPageEvent(new SheJiGroundImage()); //这里初始化分页监听类
        try {
            XMLWorkerHelper.getInstance().parseXHtml(writer,document,
                    new ByteArrayInputStream(htmlString.getBytes("UTF-8")),
                    XMLWorkerHelper.class.getResourceAsStream("/default.css"),
                    Charset.forName("UTF-8"),fontProvider);
        } catch (IOException e) {
            e.printStackTrace();
            throw new SimpleException("PDF文件生成异常");
        }finally {
            document.close();
        }

    }

4.2.5 设置水印和分业  新建一个类继承pdfPageEventHelper ,该类用来监听pdf分页 代码如下

package com.rxjy.commons.pdf;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfWriter;
import com.rxjy.constructionContract.myutils.PathUtil;

import java.io.IOException;

/**
 * @author:change
 * @description:监听分页
 * @date:2018/7/2 0002
 */
public class BackGroundImage extends PdfPageEventHelper {

    String classpath= PathUtil.getRootPath();
    String ulr=classpath.substring(5,classpath.length());

    @Override
    public void onEndPage(PdfWriter writer, Document document) {
        try {
            if(document.getPageNumber()==1){ //第一页
                Image image = Image.getInstance(ulr+"templates/合同.files/合同39.png"); //甲方
                Image image1 = Image.getInstance(ulr+"templates/合同.files/合同242.png"); //乙方
                /* 设置图片的大小 */
                image.scaleAbsolute(128, 128);
                image1.scaleAbsolute(128, 128);
                /* 设置图片的位置 */
                image.setAbsolutePosition(70,600);
                image1.setAbsolutePosition(300,600);
                document.add(image);
                document.add(image1);
                //二维码
                Image code = Image.getInstance(ulr+"templates/合同.files/合同二维码.png");
                code.scaleAbsolute(50,50);
                code.setAbsolutePosition(500,750);
                document.add(code);
            }
            if(document.getPageNumber()==4){ //第四页
                Image image = Image.getInstance(ulr+"templates/合同.files/合同39.png"); //甲方
                Image image1 = Image.getInstance(ulr+"templates/合同.files/合同242.png"); //乙方
                /* 设置图片的大小 */
                image.scaleAbsolute(128, 128);
                image1.scaleAbsolute(128, 128);
                 /* 设置图片的位置 */
                image.setAbsolutePosition(70,350);
                image1.setAbsolutePosition(300,350);
                document.add(image);
                document.add(image1);
            }
            //分页水印
            Image yejiao = Image.getInstance(ulr+"templates/合同.files/合同"+document.getPageNumber()+".png");
            yejiao.scaleAbsolute(144,44);
            yejiao.setAbsolutePosition(450,30);
            document.add(yejiao);
        } catch (BadElementException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }

    }
}

4.2.6 调用PDF工具类 代码如下

@ApiOperation("导出合同")
    @GetMapping("/importPdfByTemplates")
    void importPdfByTemplates(HttpServletResponse response,String rwdid,String templateName)throws Exception{
        Map<String,Object> map=heTongService.htInfoMap(rwdid); //该数据的key对应模版中的${}中的字段
  
        PdfUtil.exportToResponse(templateName,map,response);
    } 

五 测试

导出的PDF若遇到样式上的细微问题。可以在ftl中使用html代码调整  咨询相关问题请加群.

效果图

 

 

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值