1. 程式人生 > >flying-saucer + iText + Freemarker實現pdf的導出, 支持中文、css以及圖片

flying-saucer + iText + Freemarker實現pdf的導出, 支持中文、css以及圖片

pri 操作系統 列表 依賴 hide 屬性 css data writer

前言

項目中有個需求,需要將合同內容導出成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>
View Code

  動態數據的獲取

    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

flying-saucer + iText + Freemarker實現pdf的導出, 支持中文、css以及圖片