英文:
How to use custom font using openHtmlToPdf?
问题
我正在使用Spring Boot开发一个微服务,使用FreeMarker和openHtmlToPdf库生成PDF。我想引入自定义字体(泰米尔语言),但是我只得到####作为输出。不确定我做错了什么。
将HTML转换为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模板
<!DOCTYPE html>
<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>欢迎来到FreeMarker ${name}</h1>
</body>
</html>
英文:
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
得分: 1
以下是已翻译的内容:
font-family
不匹配
你在 Kotlin 代码中定义了字体如下:
.useFont(FONT_FILE, "Nota Sans") // font-family 将是 'Nota Sans'
但在 HTML 片段中使用了以下 CSS:
<style>
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: url(./fonts/NotoSansTamil.ttf);
}
</style>
值得一提的是,这个字体叫做 Noto 而不是 Nota。
解决方案:在 HTML 片段中使用与 Kotlin 代码中定义的相同字体系列名称。
错误的 File
路径
你所定义的字体路径是完全错误的。
我假设你的项目布局类似于 Maven / Gradle 建议的方式。
在这种情况下,resources
文件夹应该是 [project_root]/src/main/resources
val FONT_FILE = File("resources/fonts/NotoSansTamil.ttf")
如果我没错的话,字体应该位于
[project_root]/src/main/resources/fonts/NotoSansTamil.ttf
但你的 FONT_FILE
会指向 [project_root]/resources/fonts/NotoSansTamil.ttf
。
混乱的基础 URL
你构建并将 baseUrl
传递给 PdfRenderedBuilder
以解析资源。
val baseUrl = javaClass
.protectionDomain
.codeSource
.location
.toString()
如果你的布局类似于 Maven / Gradle,那么它指向 [project_root]/target/classes/
。这在其他机器上永远不会生效,而且当你将代码打包成一个构件时也不会生效。
将字体加载为文件而不是资源
val FONT_FILE = File("resources/fonts/NotoSansTamil.ttf")
这段代码指定了文件系统上的文件,但你需要一个通常与构建文件一起发布的资源。
解决方案
val fontUrl = object {}.javaClass
.getResource("fonts/NotoSansTamil.ttf")?.toURI()!!
val fontFile = File(fontUrl)
混合字体解析概念
你同时在渲染器 .useFont(...)
和 HTML / CSS 侧使用了 src: url(...);
这是一个坏主意,不会正常工作。
解决方案:只选择一种方法。要么使用 builder.useFont
要么使用 @font-face
规则,不要同时使用两者。我建议使用 builder.useFont
。在这种情况下,@font-face
定义和混乱的 baseUrl
不再需要。
将所有问题解决
修复了前面提到的所有问题,以下代码将正常工作。
注意:我以前从未编写过 Kotlin 代码,可能可以更好、优化等。
<!DOCTYPE html PUBLIC "-//OPENHTMLTOPDF//DOC XHTML Character Entities Only 1.0//EN" "">
<html lang="en">
<head>
<meta charset="UTF-16"/>
<title>FreeMarker</title>
<style>
/*
从 @font-face 更改
没有传递 src:,它将在渲染器端解析
*/
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 内容为字符串
fun main() {
val outFile = createTempFile("sample_", ".pdf")
// 使用资源作为字体
val fontUrl = object {}.javaClass
.getResource("fonts/NotoSansTamil.ttf")?.toURI()!!
PdfRendererBuilder()
.withHtmlContent(html, null) // 不再传递 baseUrl
.useFont(File(fontUrl), "noto-sans") // 匹配 font-family
.toStream(outFile.outputStream())
.run()
println("PDF 文件已创建在:$outFile")
getDesktop().open(outFile.toFile())
}
英文:
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())
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论