spring-bootpdfitextthymeleafflying-saucer

UTF-8 characters not shown in the PDF generated from html template via flying saucer and thymeleaf


I'm trying to generate a pdf contains Chinese UTF-8 characters via flying saucer and thymeleaf. But the generated pdf just ignore all the Chinese characters (latin is fine). Here is the Thymeleaf configuration

@Configuration
public class ThymeleafConfig {

    @Bean
    public ClassLoaderTemplateResolver fileTemplateResolver(){
        ClassLoaderTemplateResolver fileTemplateResolver = new ClassLoaderTemplateResolver();
        fileTemplateResolver.setPrefix("templates/");
        fileTemplateResolver.setTemplateMode("HTML");
        fileTemplateResolver.setSuffix(".html");
        fileTemplateResolver.setCharacterEncoding("UTF-8");
        fileTemplateResolver.setOrder(1);
        return fileTemplateResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine(){
        SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
        springTemplateEngine.setEnableSpringELCompiler(true);
        springTemplateEngine.setTemplateResolver(fileTemplateResolver());
        return springTemplateEngine;
    }

    @Bean
    public ThymeleafViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine());
        resolver.setCharacterEncoding("UTF-8");
        return resolver;
    }
}

As you can see, I've set the character encoding to UTF-8 for both template resolver and view resolver.

And the PDF Util for generating pdfs

public class PDFUtil {

    @Autowired
    private TemplateEngine templateEngine;

    public String createPdf(String templatename, String fileName, String modelName, Object model) throws IOException, DocumentException  {
        String fileNameUrl = "";
        Context ctx = new Context();

        ctx.setVariable(modelName, model);

        String processedHtml = templateEngine.process(templatename, ctx);
        FileOutputStream outputStream = null;

        try {
            final File outputFile = File.createTempFile(fileName, ".pdf");
            outputStream = new FileOutputStream(outputFile);

            ITextRenderer renderer = new ITextRenderer();
            ITextFontResolver resolver = renderer.getFontResolver();

            final ClassPathResource fonts = new ClassPathResource("fonts/PingFangSCRegular.ttf");
            String test = fonts.getFilename();
            resolver.addFont(fonts.getPath(), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

            renderer.setDocumentFromString(processedHtml);
            renderer.layout();
            renderer.createPDF(outputStream, false);
            renderer.finishPDF();
            FileSystemResource resource = new FileSystemResource(outputFile);
            fileNameUrl = resource.getURL().toString();
        }
        finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) { }
            }
        }

        return fileNameUrl;
    }
}

Here I've added the Chinese font to the resolver.

And this is the template html head

<head th:fragment="html_head">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://unpkg.com/jsbarcode@latest/dist/JsBarcode.all.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/fontawesome.min.css" />
    <style type="text/css">
        @font-face {
            font-family: 'PingFang SC Regular';
            src: url('/fonts/PingFangSCRegular.ttf');
            -fs-pdf-font-embed: embed;
            -fs-pdf-font-encoding: Identity-H;
        }
    </style>

</head>

So I tried to declare that the charset is UTF-8 and the font family is PingFang SC Regular. But no surprise that does not work.

Here is the maven dependency I've added to my springboot project

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.1.22</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13.3</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.13.3</version>
        </dependency>

The html body I want to render

<body>
    <div th:fragment="header(model)">
        <div class="row">
            <div class="col-4">
                <img id="barcode" alt="111"/>
            </div>
            <div class="col-4">
                <h3>啊啊啊啊啊啊aaa[[${model.title}]]</h3>
                <p id="print-time">[[${model.printTime}]]</p>
            </div>
        </div>
    </div>
</body>

And the rendered result enter image description here

Can anyone figure out why the UTF-8 character does not show in the generated pdf? Any idea would be appreciate.


Solution

  • In your template, you declare the font-face, but you don't apply it to the content of the page.

    You just have to declare that the font should be used:

    body {font-family: 'PingFang SC Regular';}
    

    Also, you don't need to use @font-face in the template, as you have added the font to the renderer (using resolver.addFont).

    The following HTML should work fine:

    <html>
        <head>
            <style>
                body {font-family: 'PingFang SC Regular';}
            </style>
        </head>
        <body>
            <h3>啊啊啊啊啊啊</h3>
        </body>
    </html>