4

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

    <!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>

// server side code

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();
        }
    }
selvi
  • 1,271
  • 2
  • 21
  • 41

1 Answers1

1

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:

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

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("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();
}

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:

enter image description here

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

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • I tried above your code but Still facing the same issue – selvi Feb 10 '21 at 13:24
  • I am sorry to hear that, it should have worked. Do you get any error? Do you experiment any improvement in the way the information is printed? Please, how do you actually print the document to PDF? In your code, I can only see `window.print()`, but with no reference to any server side code involved in the operation. I assume you perform some kind of HTTP call to the server side of your application. – jccampanero Feb 10 '21 at 13:56
  • It's showing error in this line org.xml.sax.SAXParseException: Premature end of file. But I have checked the end of the html it's working fine. The layout works fine preview page, but css getting missed in download pdf and print view. Even I am getting error while hitting download interface alone. – selvi Feb 11 '21 at 07:05
  • @ jccampanero I have attached the server-side code also.Refer above – selvi Feb 11 '21 at 07:07
  • 1
    Hi @selvi. Sorry for the late reply. Thank you very much for updating the question. I am sorry to hear that you are facing problems while processing the HTML, I suppose, when trying tidying the HTML. I update the answer with a more up to date library version. Please, can you try again and see it the error is gone? Thank you, sorry for the inconveniences. – jccampanero Feb 12 '21 at 10:25
  • jccampanero What is the import of this two lines . I was trying to import apache, Isn't it? Document htmlDOM = tidy.parseDOM(new ByteArrayInputStream(html.getBytes()), null); OutputStream out = new ByteArrayOutputStream();. – selvi Feb 18 '21 at 11:54
  • 1
    Hi @selvi. Please, see the updated answer. – jccampanero Feb 18 '21 at 12:23