Flying-saucer + iText + Freemarker implements pdf export, supports Chinese, css and pictures

Keywords: Java Maven Apache FreeMarker

Preface

There is a requirement in the project to export the contract content to pdf. IText is an open source Java library for generating PDF documents. It can dynamically generate PDF from XML or database. It can also encrypt documents, control privileges, and support Java/C# etc. But the HTML parser provided by iText itself is not strong enough. Many HTML tags and attributes can not be recognized. More sadly, simple CSS does not recognize it. Understanding, typesetting adjustment style makes people head-on. Is there any way to support css? Also refer to flying-saucer, flying-saucer is also a solution to export PDF, and is based on iText open source API, and the implementation of CSS parser, can support CSS 2.1, as well as a small number of CSS. The final solution is flying-saucer + iText + Freemarker.

Concrete realization

The process is as follows

  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>
        
        <!-- Support Chinese -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <!-- Support css Style rendering -->
        <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. Output of html content

Template.html template file

<html>
<head>
<title>${title}</title>
<!-- link Links should write file server addresses. For demonstration purposes, the 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 Links should write file server addresses. For demonstration purposes, the localhost -->
            <img alt="Loading..." 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">Baidu</a>
    </div>
</body>
</html>

Dynamic data acquisition

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

        // Get data from the database. For demonstration purposes, the data is not obtained from the database, but written to death directly.
        
        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", "User list");
        variables.put("userList", userList);
        
        return variables;
    }

Dynamic data binding, html content output

/**
     * 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. Export of 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 Content analysis
        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) {
                            //Typeface  
                            String fontCn = getChineseFont();  
                            BaseFont bf;
                            try {
                                //Note that there is one here.,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;
            }
        };

        // Picture parsing
        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 analysis
        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) {
                }
            }

            // analysis 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. Generated pdf

   1508383793597.pdf

Attention points

1. Code in blog is not a complete project, and it can't run only depending on code in blog;

2. There are differences between local files and remote files in access to file paths. In addition, there are many ways to access local files.

3. Complete Engineering Address: itextpdf readme.txt, there are many versions in the project, and this blog corresponds to version 4.

4. It is recommended that SIMSUN.TTC be put into the project, which is independent of the operating system and more portable.

Reference resources

  Get the java project root directory

  freemarker+Flying sauser +Itext integration to generate PDF

Posted by hussainz2000 on Thu, 13 Dec 2018 23:12:06 -0800