专栏首页开发技术flying-saucer + iText + Freemarker实现pdf的导出, 支持中文、css以及图片

flying-saucer + iText + Freemarker实现pdf的导出, 支持中文、css以及图片

前言

      项目中有个需求,需要将合同内容导出成pdf。上网查阅到了 iText , iText 是一个生成PDF文档的开源Java库,能够动态的从XML或者数据库生成PDF,同时还可以对文档进行加密,权限控制,并且还支持Java/C#等,但是iText本身提供的HTML解析器还是不够强大,许多HTML标签和属性无法识别,更悲催的是简单的CSS它不认识,排版调整样式让人头大。那么有没有什么方式能够支持css呢,又查阅到了 flying-saucer, flying-saucer也是导出PDF的一种解决方案,并且是基于iText的开源API,并且实现了CSS解析器,能够很好的支持CSS2.1,以及少量的CSS。最终解决方案定为: flying-saucer + iText +  Freemarker。

具体实现

  流程如下

  pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.yzb.lee</groupId>
    <artifactId>itextpdf</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>itextpdf Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.20</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.1</version>
        </dependency>
        
        <!-- 支持中文 -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <!-- 支持css样式渲染 -->
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf-itext5</artifactId>
            <version>9.0.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>itextpdf</finalName>
    </build>
</project>

  1、html内容的输出

    模版文件fileTemplate.html

<html>
<head>
<title>${title}</title>
<!-- link链接应该写文件服务器地址, 出于演示,这里用的localhost -->
<link type="text/css" rel="stylesheet" href="http://localhost:8080/itextpdf/css/pdf.css" />
<style>
@page {
    size: 8.5in 11in;
    @
    bottom-center
    {
        content
        :
        "page "
        counter(
        page
        )
        " of  "
        counter(
        pages
        );
    }
}
</style>
</head>
<body>
    <h1>Just a blank page.</h1>
    <div style="page-break-before: always;">
        <div align="center">
            <h1>${title}</h1>
            <!-- src链接应该写文件服务器地址, 出于演示,这里用的localhost -->
            <img alt="加载中..." src="http://localhost:8080/itextpdf/images/aloner.jpg" />
        </div>
        <table>
            <tr>
                <td><b>Name</b></td>
                <td><b>Age</b></td>
                <td><b>Sex</b></td>
            </tr>
            <#list userList as user>
            <tr>
                <td>${user.name}</td>
                <td>${user.age}</td>
                <td><#if user.sex = 1> male <#else> female </#if></td>
            </tr>
            </#list>
        </table>
    </div>
    <div>
        <a href="https://www.baidu.com/" target="_blank">百度</a>
    </div>
</body>
</html>

         动态数据的获取

    public Map<String, Object> getContent() throws IOException {

        // 从数据库中获取数据, 出于演示目的, 这里数据不从数据库获取, 而是直接写死
        
        Map<String, Object> variables = new HashMap<String, Object>(3);

        List<User> userList = new ArrayList<User>();

        User tom = new User("Tom", 19, 1);
        User amy = new User("Amy", 28, 0);
        User leo = new User("Leo", 23, 1);

        userList.add(tom);
        userList.add(amy);
        userList.add(leo);

        variables.put("title", "用户列表");
        variables.put("userList", userList);
        
        return variables;
    }

    动态数据的绑定,html内容的输出

/**
     * Generate html string.
     * 
     * @param template
     *            the name of freemarker teamlate.
     * @param variables
     *            the data of teamlate.
     * @return htmlStr
     * @throws Exception
     */
    public static String generate(String template, Map<String, Object> variables)
            throws Exception {
        Configuration config = FreemarkerConfiguration.getConfiguation();
        Template tp = config.getTemplate(template);
        StringWriter stringWriter = new StringWriter();
        BufferedWriter writer = new BufferedWriter(stringWriter);
        tp.setEncoding("UTF-8");
        tp.process(variables, writer);
        String htmlStr = stringWriter.toString();
        writer.flush();
        writer.close();
        return htmlStr;
    }

  2、pdf的导出

    private void generatePdf(String htmlStr, OutputStream out)
            throws IOException, DocumentException {
        //final ServletContext servletContext = getServletContext();

        Document document = new Document(PageSize.A4, 30, 30, 30, 30);
        document.setMargins(30, 30, 30, 30);
        PdfWriter writer = PdfWriter.getInstance(document, out);
        document.open();

        // html内容解析
        HtmlPipelineContext htmlContext = new HtmlPipelineContext(
                new CssAppliersImpl(new XMLWorkerFontProvider() {
                    @Override
                    public Font getFont(String fontname, String encoding,
                            float size, final int style) {
                        Font font = null;
                        if (fontname == null) {
                            //字体  
                            String fontCn = getChineseFont();  
                            BaseFont bf;
                            try {
                                //注意这里有一个,1 
                                bf = BaseFont.createFont(fontCn+",1", 
                                         BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
                                font = new Font(bf, size, style);
                            } catch (DocumentException e) {
                                e.printStackTrace();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }  
                            
                        }
                        return font;
                    }
                })) {
            @Override
            public HtmlPipelineContext clone()
                    throws CloneNotSupportedException {
                HtmlPipelineContext context = super.clone();
                try {
                    ImageProvider imageProvider = this.getImageProvider();
                    context.setImageProvider(imageProvider);
                } catch (NoImageProviderException e) {
                }
                return context;
            }
        };

        // 图片解析
        htmlContext.setImageProvider(new AbstractImageProvider() {

            // String rootPath = servletContext.getRealPath("/");

            @Override
            public String getImageRootPath() {
                return "";
            }

            @Override
            public Image retrieve(String src) {
                if (StringUtils.isEmpty(src)) {
                    return null;
                }
                try {
                    // String imageFilePath = new File(rootPath, src).toURI().toString();
                    Image image = Image.getInstance(src);
                    image.setAbsolutePosition(400, 400);
                    if (image != null) {
                        store(src, image);
                        return image;
                    }
                } catch (Throwable e) {
                    e.printStackTrace();
                }
                return super.retrieve(src);
            }
        });
        htmlContext.setAcceptUnknown(true).autoBookmark(true)
                .setTagFactory(Tags.getHtmlTagProcessorFactory());

        // css解析
        CSSResolver cssResolver = XMLWorkerHelper.getInstance()
                .getDefaultCssResolver(true);
        cssResolver.setFileRetrieve(new FileRetrieve() {
            @Override
            public void processFromStream(InputStream in,
                    ReadingProcessor processor) throws IOException {
                try (InputStreamReader reader = new InputStreamReader(in,
                        CHARSET_NAME)) {
                    int i = -1;
                    while (-1 != (i = reader.read())) {
                        processor.process(i);
                    }
                } catch (Throwable e) {
                }
            }

            // 解析href
            @Override
            public void processFromHref(String href, ReadingProcessor processor)
                    throws IOException {
                // InputStream is = servletContext.getResourceAsStream(href);
                URL url = new URL(href);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5 * 1000);
                InputStream is = conn.getInputStream();

                try (InputStreamReader reader = new InputStreamReader(is,
                        CHARSET_NAME)) {
                    int i = -1;
                    while (-1 != (i = reader.read())) {
                        processor.process(i);
                    }
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        });

        HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext,
                new PdfWriterPipeline(document, writer));
        Pipeline<?> pipeline = new CssResolverPipeline(cssResolver,
                htmlPipeline);
        XMLWorker worker = null;
        worker = new XMLWorker(pipeline, true);
        XMLParser parser = new XMLParser(true, worker,
                Charset.forName(CHARSET_NAME));
        try (InputStream inputStream = new ByteArrayInputStream(
                htmlStr.getBytes())) {
            parser.parse(inputStream, Charset.forName(CHARSET_NAME));
        }
        document.close();
    }

  3、生成的pdf

1508383793597.pdf

注意点

  1、博客中的代码不是一个完整工程,只依赖博客中的代码是运行不起来的;  

  2、文件路径的获取,本地文件与远程文件的获取是有区别的, 另外本地文件的获取又存在多种方式;

  3、完整工程地址:itextpdf,仔细阅读readme.txt, 工程中存在多个版本, 而本博客对应的是版本4;

  4、推荐将SIMSUN.TTC放到工程中, 这就不依赖操作系统了, 可移植性更强;

参考

获取java项目根目录

freemarker+Flying sauser +Itext 整合生成PDF

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java 实现二叉树的构建以及3种遍历方法

    大二下学期学习数据结构的时候用C介绍过二叉树,但是当时热衷于java就没有怎么鸟二叉树,但是对二叉树的构建及遍历一直耿耿于怀,今天又遇见这个问题了,所以花了一下...

    Rekent
  • Java TCP通信概念及实例

                  <1>TCP:类似于电话系统,建立双向的通信通道,确定连接,话音顺序接听。

    Rekent
  • 详尽! Win10安装Java8+Tomcat9!

    Java也好, Tomcat也好, 都是很实用的啦, 早点掌握还是有必要的. 喜欢记得点赞哦, 有意见或者建议评论区哦, 当然暗中关注我也是可以的.

    SeanDepp
  • Java 通过先序中序序列生成二叉树

      二叉树的前序以及后续序列,以空格间隔每个元素,重构二叉树,最后输出二叉树的三种遍历方式的序列以验证。

    Rekent
  • Java UDP的简单实例以及知识点简述

      Java中实现UDP协议的两个类,分别是DatagramPacket数据包类以及DatagramSocket套接字类。

    Rekent
  • Java 循环队列的实现

      队列(Queue)是限定只能在一端插入、另一端删除的线性表。允许删除的一端叫做队头(front),允许插入的一端叫做队尾(rear),没有元素的队列称为“空...

    Rekent
  • Java 冒泡排序与快速排序的实现

          (1)基于交换思想的排序算法         (2)从一端开始,逐个比较相邻的两个元素,发现倒序即交换。           (3)一次遍历,一定能...

    Rekent
  • Java 线程池的实现

            任务接口:每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等。 工作线程通过该接口调度任务的执行。

    Rekent
  • 栈的Java简单实现

      进行插入和删除操作的一端称为“栈顶”(top),另一端称为“栈底”(bottom)。

    Rekent
  • 使用Solr涡轮增压您的WordPress搜索

    由于无法建议搜索短语,捕捉拼写错误,理解单词变体,组织和过滤结果以及索引搜索结果文档,因此WordPress内置的标准搜索无法为访问者提供最佳搜索体验。全文搜索...

    GongAo啊_

扫码关注云+社区

领取腾讯云代金券