CSS样式在使用Thymeleaf HTML模板在Java中下载PDF文件时不可见。

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

css styles is not visible when downloading the pdf file using thymleaf html template in java

问题

我正在使用Thymeleaf HTML模板。当我预览页面时,样式看起来很好。但是,当我下载PDF时,我看不到应用的任何CSS样式。PDF只包含内容,没有我应用的样式。

// 下载生成代码

我参考并使用了相同的PDF生成代码链接

// 示例代码

<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>Profile Preview</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css"></link>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/paper-css/0.4.1/paper.css"></link>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"></link>
<style>
@page { size: A4 }
table {
  border-collapse: collapse;
  width: 100%;
  margin-bottom: 20px;
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 5px;
}

tr:nth-child(even) {
  background-color: #dddddd
}
</style>
</head>
<body class="A4">
<div class ="preview">
<section class="sheet">
<div class="logo">
<img th:src="@{/images/logo.png}" />
</div> 
<h4 style="font-size: 1em; font-weight: bold; line-height: 1em">
<p>Age: <span th:text="${profile.basicInfo.age}"></span></p>
<p>D.O.B: <span th:text="${profile.basicInfo.birthDate}"></span></p>
<p>Gender: <span th:text="${profile.basicInfo.gender.toString()}"></span></p>
<p>Education: <span th:text="${profile.professionalInfo.educationDetail}"></span></p>
</h4>
<table>
<tr>
<th colspan="4" style="text-align:center; background-color: #6c3c81; color:white; font-weight: bold">Partner Preference Information</th>
</tr>
</table>
</div>
</section> 
</div>
<button class="button" onClick="window.print();this.style.display='none'">Print</button>
</body>
</html>

// 服务器端代码

@GetMapping("/{id}/download")
public void download(@PathVariable("id") String id, HttpServletResponse response) {
    try {
        Path file = Paths.get(profilePdfService.generatePdf(id).getAbsolutePath());
        if (Files.exists(file)) {
            response.setContentType("application/pdf");
            response.addHeader("Content-Disposition", "attachment; filename=" + file.getFileName());
            Files.copy(file, response.getOutputStream());
            response.getOutputStream().flush();
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}
英文:

I am using thymleaf html template. When I preview the page the styling looks good. When I download the pdf, I don't see any CSS styles applied. The pdf contains content only not the style which I have applied.

// download generation code

Pdf generation code link which I referred and used the same

// sample code

    &lt;!DOCTYPE HTML&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;
xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;&lt;/meta&gt;
&lt;title&gt;Profile Preview&lt;/title&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css&quot;&gt;&lt;/link&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdnjs.cloudflare.com/ajax/libs/paper-css/0.4.1/paper.css&quot;&gt;&lt;/link&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css&quot;&gt;&lt;/link&gt;
&lt;style&gt;
@page { size: A4 }
table {
border-collapse: collapse;
width: 100%;
margin-bottom: 20px;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 5px;
}
tr:nth-child(even) {
background-color: #dddddd
}
&lt;/style&gt;
&lt;/head&gt;        
&lt;body class=&quot;A4&quot;&gt;
&lt;div class =&quot;preview&quot;&gt;
&lt;section class=&quot;sheet&quot;&gt;
&lt;div class=&quot;logo&quot;&gt;
&lt;img th:src=&quot;@{/images/logo.png}&quot; /&gt;
&lt;/div&gt; 
&lt;h4 style=&quot;font-size: 1em; font-weight: bold; line-height: 1em&quot;&gt;
&lt;p&gt;Age: &lt;span th:text=&quot;${profile.basicInfo.age}&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;D.O.B: &lt;span th:text=&quot;${profile.basicInfo.birthDate}&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Gender: &lt;span th:text=&quot;${profile.basicInfo.gender.toString()}&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Education: &lt;span th:text=&quot;${profile.professionalInfo.educationDetail}&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/h4&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th colspan=&quot;4&quot; style=&quot;text-align:center; background-color: #6c3c81; color:white; font-weight: bold&quot;&gt;Partner Preference Information&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/section&gt; 
&lt;/div&gt;
&lt;button class=&quot;button&quot; onClick=&quot;window.print();this.style.display=&#39;none&#39;&quot;&gt;Print&lt;/button&gt;
&lt;/body&gt;
&lt;/html&gt;

// server side code

GetMapping(&quot;/{id}/download&quot;)
public void download(@PathVariable(&quot;id&quot;) String id, HttpServletResponse response) {
try {
Path file = Paths.get(profilePdfService.generatePdf(id).getAbsolutePath());
if (Files.exists(file)) {
response.setContentType(&quot;application/pdf&quot;);
response.addHeader(&quot;Content-Disposition&quot;, &quot;attachment; filename=&quot; + file.getFileName());
Files.copy(file, response.getOutputStream());
response.getOutputStream().flush();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}

答案1

得分: 1

以下是翻译好的内容:

似乎您正在使用Flying Saucer以PDF格式呈现生成的Thymeleaf模板HTML。

这里可能会有几个问题。

首先,您需要向Flying Source提供正确的XHTML文档。为此,您可以使用JTidy将渲染的Thymeleaf模板转换为XHTML:在非常复杂的HTML中可能无法正常工作,但在您的用例中很可能会正常工作。

有许多版本的JTidy。抱歉,在之前版本的答案中,我为您提供了一个过时的引用,可能无法适用于您的HTML5需求。请改为使用以下依赖项,基于全新的JTidy 项目

<dependency>
  <groupId>com.github.jtidy</groupId>
  <artifactId>jtidy</artifactId>
  <version>1.0.2</version>
</dependency>

例如,在您在问题中指示的PdfService类的源代码中,将generatePdf方法修改如下:

public File generatePdf() throws IOException, DocumentException {
  Context context = getContext();
  String html = loadAndFillTemplate(context);
  String xhtml = convertToXhtml(html);
  return renderPdf(xhtml);
}

其中convertToXhtml可以如下所示(这是适用于新JTidy库版本的更新版本):

// 必要的导入,以及其他内容
import org.w3c.dom.Document;
import org.w3c.tidy.Tidy;

//...

private String convertToXhtml(String html) throws UnsupportedEncodingException {
  Tidy tidy = new Tidy();
  tidy.setXHTML(true);
  tidy.setIndentContent(true);
  tidy.setPrintBodyOnly(true);
  tidy.setInputEncoding("UTF-8");
  tidy.setOutputEncoding("UTF-8");
  tidy.setSmartIndent(true);
  tidy.setShowWarnings(false);
  tidy.setQuiet(true);
  tidy.setTidyMark(false);

  Document htmlDOM = tidy.parseDOM(new ByteArrayInputStream(html.getBytes()), null);

  OutputStream out = new ByteArrayOutputStream();
  tidy.pprint(htmlDOM, out);
  return out.toString();
}

请注意,在执行此过程后页面的外观:您正在应用许多内联样式,PDF应该会呈现得更好。

此外,不要使用外部CDN分发的样式表,而是使用本地副本,可通过您的应用程序作为资源来访问,该资源在PdfService类的源代码中被标识为PDF_RESOURCES

正如您在该类的renderPdf实现中所看到的那样,它被用作查找页面中引用的不同资源的基本URL。

最后,注意您的徽标:也许您需要为此提供一些ReplacedElementFactory的自定义实现。请考虑阅读这个这个 SO问题,我认为它们可能会有帮助。

遵循这些步骤,稍作调整,您应该能够获得类似以下PDF的内容:

(图片链接在原文中,请查阅原文)

如果Flying Saucer不能满足您的要求,请考虑使用任何无头浏览器,例如PhantomJS,进行转换。

英文:

It seems you are using Flying Saucer for rendering in PDF format the generated Thymeleaf template HTML.

There can be several issues here.

First, you need to provide a proper XHTML document to Flying Source. For this task, you can use JTidy to convert the rendered Thymeleaf template to XHTML: it maybe does not work in a very complicated HTML, but it very likely will in your use case.

There are a lot of versions of JTidy. Sorry, in a previous version of the answer I provided you a reference to an outdated one I previously used, that maybe does not work for HTML5 as your need. Please, use the following dependency instead, based on a brand new JTidy project:

&lt;dependency&gt;
  &lt;groupId&gt;com.github.jtidy&lt;/groupId&gt;
  &lt;artifactId&gt;jtidy&lt;/artifactId&gt;
  &lt;version&gt;1.0.2&lt;/version&gt;
&lt;/dependency&gt;

For example, in the source code of the PdfService class you indicated in the question, modify the generatePdf method as follows:

public File generatePdf() throws IOException, DocumentException {
  Context context = getContext();
  String html = loadAndFillTemplate(context);
  String xhtml = convertToXhtml(html);
  return renderPdf(xhtml);
}

Where convertToXhtml can look like (is an updated version of the previous one adapted for the new JTidy library version):

// Necessary imports, among others
import org.w3c.dom.Document;
import org.w3c.tidy.Tidy;

//...

private String convertToXhtml(String html) throws UnsupportedEncodingException {
  Tidy tidy = new Tidy();
  tidy.setXHTML(true);
  tidy.setIndentContent(true);
  tidy.setPrintBodyOnly(true);
  tidy.setInputEncoding(&quot;UTF-8&quot;);
  tidy.setOutputEncoding(&quot;UTF-8&quot;);
  tidy.setSmartIndent(true);
  tidy.setShowWarnings(false);
  tidy.setQuiet(true);
  tidy.setTidyMark(false);

  Document htmlDOM = tidy.parseDOM(new ByteArrayInputStream(html.getBytes()), null);

  OutputStream out = new ByteArrayOutputStream();
  tidy.pprint(htmlDOM, out);
  return out.toString();
}

See how the page looks like after performing this process: you are applying a lot of inline styles and the PDF should look better.

Also, instead of using external CDN distributed stylesheets, use a local copy of them, accessible to your application as the resource identified as PDF_RESOURCES in the source code of the PdfService class.

As you can see in the implementation of the renderPdf of that class, it is being used as the base URL to look for the different resources referenced in the page.

Finally, pay attention to your logo: perhaps you will need to provide some custom implementation of ReplacedElementFactory for that purpose. Please, consider read this or this other SO questions, I think they can be helpful.

Following these steps, with minor tweaks, you should be able to obtain something like the following PDF:

CSS样式在使用Thymeleaf HTML模板在Java中下载PDF文件时不可见。

If Flying Saucer does not fulfill your requirements, please, take a look at any headless browser, for instance, PhantomJS, to perform the conversion.

huangapple
  • 本文由 发表于 2020年10月26日 15:08:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/64532755.html
匿名

发表评论

匿名网友

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

确定