0

I am developing a microservice in springboot to generate pdf by using freeMarker and openHtmlToPdf libraries. I want to introduce custom font(tamil language). But in only getting #### as output. Not sure where I am going wrong.

Method converting html to pdf

private fun convertToPdf(htmlContent: String): ByteArrayResource {
 val jsoupDocument = Jsoup.parse(htmlContent)
 jsoupDocument.outputSettings().syntax(Document.OutputSettings.Syntax.html)
 val xmlDocument = W3CDom().fromJsoup(jsoupDocument)

 val FONT_FILE = File("resources/fonts/NotoSansTamil.ttf")

 val byteArrayOutputStream = ByteArrayOutputStream()
 val baseUrl = javaClass
 .protectionDomain
 .codeSource
 .location
 .toString()

 PdfRendererBuilder()
 .withW3cDocument(xmlDocument, baseUrl)
 .useFont(FONT_FILE, "Nota Sans")
 .toStream(byteArrayOutputStream)
 .run()
 return ByteArrayResource(byteArrayOutputStream.toByteArray())
} 

Freemarker template


<html>
<head>
<#--    <meta charset="UTF-16">-->
    <title>FreeMarker</title>
    <style>
        @font-face {
            font-family: 'Open Sans';
            font-style: normal;
            font-weight: 400;
            src: url(./fonts/NotoSansTamil.ttf);
        }
    </style>
</head>
<body>
    <h1> Welcome to FreeMarker ${name} </h1>
</body>
</html>

1 Answers1

1

There are multiple different problems that code.

font-family mismatch

You defined font as

.useFont(FONT_FILE, "Nota Sans") //font-family will be 'Nota Sans'

but in the hmtl frament using

<style>
    @font-face {
        font-family: 'Open Sans';
        font-style: normal;
        font-weight: 400;
        src: url(./fonts/NotoSansTamil.ttf);
    }
</style>

Just for the record this font called Noto instead of Nota.

Solution: Always use the same font family name in html fragment as defined in Kotlin code.

Wrong File path

As you defined the font is totally wrong. I assume your project layout is like Maven / Gradle suggest. In this case the resources folder should be [project_root]/src/main/resources

val FONT_FILE = File("resources/fonts/NotoSansTamil.ttf")

If I'm right then the font should be at [project_root]/src/main/resources/fonts/NotoSansTamil.ttf but your FONT_FILE will point to [project_root]/resources/fonts/NotoSansTamil.ttf.

Messy Base URL

You build and pass baseUrl to PdfRenderedBuilder to resolve resources.

val baseUrl = javaClass
    .protectionDomain
    .codeSource
    .location
    .toString()

If your layout is Maven / Gradle like, then it points to [project_root]/target/classes/. It will never work on the other machine and it won't work when you pakcage your code into an artifact.

Loading font as file not a resource

val FONT_FILE = File("resources/fonts/NotoSansTamil.ttf")

This snippet specify a file on the file system, but you need a resource which is usually shipped with that artifact.

Solution

val fontUrl = object {}.javaClass
    .getResource("fonts/NotoSansTamil.ttf")?.toURI()!!
val fontFile = File(fontUrl)

Mixing font resolution concepts

You added a font both renderer .useFont(...) and html / css side src: url(...); It's a bad idea and won't work properly.

Solution: Choose only one way. Use either builder.useFont or a @font-face rule, not both. I suggest use builder.useFont. In this case @font-face definition and messy baseUrl are no longer required.

Putting all together

Fixing all previously mentioned problems the following code will work.

NOTE: I have never written Kotlin code before, probably it could be made better, optimized, etc.

<!DOCTYPE html PUBLIC "-//OPENHTMLTOPDF//DOC XHTML Character Entities Only 1.0//EN" "">

<html lang="en">
<head>
    <meta charset="UTF-16"/>
    <title>FreeMarker</title>
    <style>
        /*
            changed from @font-face
            no src: passed, it will be resolved at renderer side
         */
        body {
            font-family: noto-sans, sans-serif;
            font-style: normal;
            font-weight: 400;
        }
    </style>
</head>
<body>
    <h1> Welcome to FreeMarker!</h1>
</body>
</html>
private const val html = """ ... """ // HTML content as Stirng

fun main() {
    val outFile = createTempFile("sample_", ".pdf")
    // using font as resource
    val fontUrl = object {}.javaClass
        .getResource("fonts/NotoSansTamil.ttf")?.toURI()!!

    PdfRendererBuilder()
        .withHtmlContent(html, null) // baseUrl no longer passed
        .useFont(File(fontUrl), "noto-sans") //matching font-family
        .toStream(outFile.outputStream())
        .run()
    println("PDF file created at: $outFile")
    getDesktop().open(outFile.toFile())
}
zforgo
  • 2,508
  • 2
  • 14
  • 22
  • I am still facing some issue in the final output. After some research I found it is because of some encoding issue. Can't figure it out. Do you have any suggestion? PDF link: https://pdf.ac/18pm1j – Peaky Blinder Apr 08 '23 at 10:54
  • I tried to use IText, just in case openHtmlToPdf isn't supporting unicode font styles. Same result there too. – Peaky Blinder Apr 08 '23 at 10:56
  • 1
    1. OpenHtmlToPdf supports unidoce font styles, but not supports iText. 2. That link shows an image instead of a PDF. But that image perfectly shows different fonts. Finally, I never will register to any site to get a raw pdf file. – zforgo Apr 08 '23 at 11:04