如何在openHtmlToPdf中使用自定义字体?

huangapple go评论109阅读模式
英文:

How to use custom font using openHtmlToPdf?

问题

我正在使用Spring Boot开发一个微服务,使用FreeMarker和openHtmlToPdf库生成PDF。我想引入自定义字体(泰米尔语言),但是我只得到####作为输出。不确定我做错了什么。

将HTML转换为PDF的方法

  1. private fun convertToPdf(htmlContent: String): ByteArrayResource {
  2. val jsoupDocument = Jsoup.parse(htmlContent)
  3. jsoupDocument.outputSettings().syntax(Document.OutputSettings.Syntax.html)
  4. val xmlDocument = W3CDom().fromJsoup(jsoupDocument)
  5. val FONT_FILE = File("resources/fonts/NotoSansTamil.ttf")
  6. val byteArrayOutputStream = ByteArrayOutputStream()
  7. val baseUrl = javaClass
  8. .protectionDomain
  9. .codeSource
  10. .location
  11. .toString()
  12. PdfRendererBuilder()
  13. .withW3cDocument(xmlDocument, baseUrl)
  14. .useFont(FONT_FILE, "Nota Sans")
  15. .toStream(byteArrayOutputStream)
  16. .run()
  17. return ByteArrayResource(byteArrayOutputStream.toByteArray())
  18. }

FreeMarker模板

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <!--<meta charset="UTF-16">-->
  5. <title>FreeMarker</title>
  6. <style>
  7. @font-face {
  8. font-family: 'Open Sans';
  9. font-style: normal;
  10. font-weight: 400;
  11. src: url(./fonts/NotoSansTamil.ttf);
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <h1>欢迎来到FreeMarker ${name}</h1>
  17. </body>
  18. </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

  1. private fun convertToPdf(htmlContent: String): ByteArrayResource {
  2. val jsoupDocument = Jsoup.parse(htmlContent)
  3. jsoupDocument.outputSettings().syntax(Document.OutputSettings.Syntax.html)
  4. val xmlDocument = W3CDom().fromJsoup(jsoupDocument)
  5. val FONT_FILE = File(&quot;resources/fonts/NotoSansTamil.ttf&quot;)
  6. val byteArrayOutputStream = ByteArrayOutputStream()
  7. val baseUrl = javaClass
  8. .protectionDomain
  9. .codeSource
  10. .location
  11. .toString()
  12. PdfRendererBuilder()
  13. .withW3cDocument(xmlDocument, baseUrl)
  14. .useFont(FONT_FILE, &quot;Nota Sans&quot;)
  15. .toStream(byteArrayOutputStream)
  16. .run()
  17. return ByteArrayResource(byteArrayOutputStream.toByteArray())
  18. }

Freemarker template

  1. &lt;html&gt;
  2. &lt;head&gt;
  3. &lt;#-- &lt;meta charset=&quot;UTF-16&quot;&gt;--&gt;
  4. &lt;title&gt;FreeMarker&lt;/title&gt;
  5. &lt;style&gt;
  6. @font-face {
  7. font-family: &#39;Open Sans&#39;;
  8. font-style: normal;
  9. font-weight: 400;
  10. src: url(./fonts/NotoSansTamil.ttf);
  11. }
  12. &lt;/style&gt;
  13. &lt;/head&gt;
  14. &lt;body&gt;
  15. &lt;h1&gt; Welcome to FreeMarker ${name} &lt;/h1&gt;
  16. &lt;/body&gt;
  17. &lt;/html&gt;

答案1

得分: 1

以下是已翻译的内容:

font-family 不匹配

你在 Kotlin 代码中定义了字体如下:

  1. .useFont(FONT_FILE, "Nota Sans") // font-family 将是 'Nota Sans'

但在 HTML 片段中使用了以下 CSS:

  1. <style>
  2. @font-face {
  3. font-family: 'Open Sans';
  4. font-style: normal;
  5. font-weight: 400;
  6. src: url(./fonts/NotoSansTamil.ttf);
  7. }
  8. </style>

值得一提的是,这个字体叫做 Noto 而不是 Nota。

解决方案:在 HTML 片段中使用与 Kotlin 代码中定义的相同字体系列名称。

错误的 File 路径

你所定义的字体路径是完全错误的。
我假设你的项目布局类似于 Maven / Gradle 建议的方式。
在这种情况下,resources 文件夹应该是 [project_root]/src/main/resources

  1. 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 以解析资源。

  1. val baseUrl = javaClass
  2. .protectionDomain
  3. .codeSource
  4. .location
  5. .toString()

如果你的布局类似于 Maven / Gradle,那么它指向 [project_root]/target/classes/。这在其他机器上永远不会生效,而且当你将代码打包成一个构件时也不会生效。

将字体加载为文件而不是资源

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

这段代码指定了文件系统上的文件,但你需要一个通常与构建文件一起发布的资源。

解决方案

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

混合字体解析概念

你同时在渲染器 .useFont(...) 和 HTML / CSS 侧使用了 src: url(...); 这是一个坏主意,不会正常工作。

解决方案:只选择一种方法。要么使用 builder.useFont 要么使用 @font-face 规则,不要同时使用两者。我建议使用 builder.useFont。在这种情况下,@font-face 定义和混乱的 baseUrl 不再需要。

将所有问题解决

修复了前面提到的所有问题,以下代码将正常工作。

注意:我以前从未编写过 Kotlin 代码,可能可以更好、优化等。

  1. <!DOCTYPE html PUBLIC "-//OPENHTMLTOPDF//DOC XHTML Character Entities Only 1.0//EN" "">
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-16"/>
  5. <title>FreeMarker</title>
  6. <style>
  7. /*
  8. 从 @font-face 更改
  9. 没有传递 src:,它将在渲染器端解析
  10. */
  11. body {
  12. font-family: noto-sans, sans-serif;
  13. font-style: normal;
  14. font-weight: 400;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <h1> Welcome to FreeMarker!</h1>
  20. </body>
  21. </html>
  1. private const val html = """ ... """ // HTML 内容为字符串
  2. fun main() {
  3. val outFile = createTempFile("sample_", ".pdf")
  4. // 使用资源作为字体
  5. val fontUrl = object {}.javaClass
  6. .getResource("fonts/NotoSansTamil.ttf")?.toURI()!!
  7. PdfRendererBuilder()
  8. .withHtmlContent(html, null) // 不再传递 baseUrl
  9. .useFont(File(fontUrl), "noto-sans") // 匹配 font-family
  10. .toStream(outFile.outputStream())
  11. .run()
  12. println("PDF 文件已创建在:$outFile")
  13. getDesktop().open(outFile.toFile())
  14. }
英文:

There are multiple different problems that code.

font-family mismatch

You defined font as

  1. .useFont(FONT_FILE, &quot;Nota Sans&quot;) //font-family will be &#39;Nota Sans&#39;

but in the hmtl frament using

  1. &lt;style&gt;
  2. @font-face {
  3. font-family: &#39;Open Sans&#39;;
  4. font-style: normal;
  5. font-weight: 400;
  6. src: url(./fonts/NotoSansTamil.ttf);
  7. }
  8. &lt;/style&gt;

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

  1. val FONT_FILE = File(&quot;resources/fonts/NotoSansTamil.ttf&quot;)

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.

  1. val baseUrl = javaClass
  2. .protectionDomain
  3. .codeSource
  4. .location
  5. .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

  1. val FONT_FILE = File(&quot;resources/fonts/NotoSansTamil.ttf&quot;)

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

Solution

  1. val fontUrl = object {}.javaClass
  2. .getResource(&quot;fonts/NotoSansTamil.ttf&quot;)?.toURI()!!
  3. 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.

  1. &lt;!DOCTYPE html PUBLIC &quot;-//OPENHTMLTOPDF//DOC XHTML Character Entities Only 1.0//EN&quot; &quot;&quot;&gt;
  2. &lt;html lang=&quot;en&quot;&gt;
  3. &lt;head&gt;
  4. &lt;meta charset=&quot;UTF-16&quot;/&gt;
  5. &lt;title&gt;FreeMarker&lt;/title&gt;
  6. &lt;style&gt;
  7. /*
  8. changed from @font-face
  9. no src: passed, it will be resolved at renderer side
  10. */
  11. body {
  12. font-family: noto-sans, sans-serif;
  13. font-style: normal;
  14. font-weight: 400;
  15. }
  16. &lt;/style&gt;
  17. &lt;/head&gt;
  18. &lt;body&gt;
  19. &lt;h1&gt; Welcome to FreeMarker!&lt;/h1&gt;
  20. &lt;/body&gt;
  21. &lt;/html&gt;
  1. private const val html = &quot;&quot;&quot; ... &quot;&quot;&quot; // HTML content as Stirng
  2. fun main() {
  3. val outFile = createTempFile(&quot;sample_&quot;, &quot;.pdf&quot;)
  4. // using font as resource
  5. val fontUrl = object {}.javaClass
  6. .getResource(&quot;fonts/NotoSansTamil.ttf&quot;)?.toURI()!!
  7. PdfRendererBuilder()
  8. .withHtmlContent(html, null) // baseUrl no longer passed
  9. .useFont(File(fontUrl), &quot;noto-sans&quot;) //matching font-family
  10. .toStream(outFile.outputStream())
  11. .run()
  12. println(&quot;PDF file created at: $outFile&quot;)
  13. getDesktop().open(outFile.toFile())
  14. }

huangapple
  • 本文由 发表于 2023年4月7日 01:49:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75952389.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定