1

I am evaluating Thymeleaf and Flying Saucer for pdf generation from templates, and I am having a problem with applying css to my Thymeleaf template. I already read the relevant questions & answers here, here, and here; but none of the suggested solutions fixed my problem.

This is how my resources folder looks like:

enter image description here

So I am using the default directories that Spring will look for. And that's how the head tag looks like in my template.html:

<head>
    <title>Spring Boot and Thymeleaf Example</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <link rel="stylesheet" type="text/css" href="../static/css/style.css" th:href="@{/css/style.css}"/> 
</head>

If I inline my css in template.html then the generated pdf file will be styled properly (so there shouldn't be a problem with how I generate the pdf). However, when I try to link to the css file as shown above the generated pdf is not styled (so the css is not applied).

Lastly, I can access my css file at http://localhost:8080/css/style.css, so there doesn't seem to be a problem with Spring serving the static content.

For completeness, this is how I generate the pdf:

private final SpringTemplateEngine templateEngine;
private final Log log;

@Autowired
public PdfGenerator(SpringTemplateEngine templateEngine) {
    this.templateEngine = templateEngine;
    log = LogFactory.getLog(getClass());
}

public void generate(HttpServletRequest servletRequest, HttpServletResponse servletResponse, ServletContext servletContext) {
    // Parse the pdf template with Thymeleaf
    Locale locale = getLocale(servletRequest);
    WebContext context = new WebContext(servletRequest, servletResponse, servletContext, locale);
    context.setVariable("user", buildDummyUser());
    context.setVariable("discounts", buildDummyDiscounts());
    String html = templateEngine.process("template", context);

    // Create the pdf with Flying Saucer
    try (OutputStream outputStream = new FileOutputStream("generated.pdf")) {
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocumentFromString(html);
        renderer.layout();
        renderer.createPDF(outputStream);
    } catch (IOException | DocumentException e) {
        log.error("Error while generating pdf", e);
    }
}

I am using WebContext instead of Context because I was getting the following error with Context:

org.thymeleaf.exceptions.TemplateProcessingException: Link base "/css/style.css" cannot be context relative (/...) unless the context used for executing the engine implements the org.thymeleaf.context.IWebContext interface

What am I missing here, why is my style.css not applied to template.html?

Egemen
  • 2,178
  • 5
  • 22
  • 32

6 Answers6

10

I had same problems and I was also trying to use thymeleaf template resolver for pdf generation. I did lots research on thymeleaf and spring framework, I tried WebContext, I tried HttpServletRequest, I tried some of Spring Thymeleaf integration solutions it was not working either. So I think it was not syntax error, and I finally end up with using absolute path instead of relative. Url for reference

Here the reason with my assumption, lets say our resources are served on localhost:8080/myapp/css/style.css. And the relative path to request resource is really ups to what context it relatives to. For eaxmple a normal thymeleaf model Veiw, which return as html pages on browser for client, so the context in that case would be the request hostname, port and application context(eg: localhost:8080/myapp). And relative path will be based on that. So if relative path is /css/style.css, context + relative path will result to be localhost:8080/myapp/css/style.css

Unlike web context, in our case, offline template is on server backend, so the context I assume would be the server running context, which would be the local server path + appcontext(eg: D:/myServer/apps/myapp), relative path /css/style.css on this would be D:/myServer/apps/myapp/css/style.css, this is not make sense. In order to use static resources, I have to pass it's absolute path.

I started use :

<link rel="stylesheet" type="text/css" th:href="@{http://localhost:8080/myapp/css/style.css}"/>

It's working fine but what if there are multiple host names or server is running on a proxy, then this is going to be a hard coded solution. It's better to know what is the real base url the user is requesting. So we can't really get rid off HttpSevletRequest.

Here is my code:

1.Config resource handler:

@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/css/**")
    .addResourceLocations("classpath:/css/")
            .setCachePeriod(31556926);
}
  1. Get base url from HttpServletRequest, you can inject it in method or autowired in your service class, or get from RequestContextHolder. I write this in my Service class:

    private static String getCurrentBaseUrl() {
    ServletRequestAttributes sra = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
    HttpServletRequest req = sra.getRequest();
    return req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + req.getContextPath();
    } 
    
  2. This is the place I use template engine in my class:

        Context context = new Context();
        context.setVariable("variales", variables);
        context.setVariable("baseUrl", getCurrentBaseUrl());
        String content = springTemplateEngine.process("myTemplate",context);
    
  3. In my template, I use absolute css url like this:

    <link type="stylesheet" th:src="@{|${baseUrl}/css/style.css|}" />
    
Chenhai-胡晨海
  • 1,315
  • 9
  • 7
  • Thanks for the answer. I could make it work with the absolute path as well, although I don't really like using absolute paths. One remark though: I used `th:href="@{|${baseUrl}/css/style.css|}"` in the template and didn't need to implement `addResourceHandlers`. – Egemen Dec 13 '18 at 09:08
  • Guess we are in same situation, I don't like absolute path either. But as far as I know, this is only way to make it work for me. For the resource handler I just want make sure static content must be served on web. This is not that bad though, because base url is totally depends on real user request, so it is still flexible. If you can find relative path solution, pls let me know. Thank you – Chenhai-胡晨海 Dec 13 '18 at 14:37
1

I solved this problem by changing the path structure in href. I had the same directory structure as you (html files are in templates doc, css files are in static doc).

<head>
    <title>Spring Boot and Thymeleaf Example</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <link rel="stylesheet" type="text/css" href="/css/style.css"/> 
</head>

It might help you to apply css to your html page.

0

Syntax looks fine so the problem is not with the syntax.

Also you cannot use @{...} syntax without an IWebContext interface so You are getting this exception.

Alien
  • 15,141
  • 6
  • 37
  • 57
  • I also think the syntax is fine, as well as the structure of the resources. Inlining the css works too, so I don't think there is a problem with the pdf generation. That's why I am puzzled, I don't see where the problem could be. – Egemen Nov 09 '18 at 12:18
0

I had a similar problem - my css was not applied to my template page.

My problem was that the css file was in css sass format

.table
   margin: 0 0 40px 0

when I convert it to the normal css format like

 .table {
  margin: 0 0 40px 0;
  }

it worked

0

I found a lazy man's way of taking care of this. It works, with a very simple approach. The 'inserted' fragment is just a CSS style tag in the body of a simple HTML document. I place this in the HEAD of my target file, right where I would have put the LINK REL tag:

<th:block th:insert="std-reports/std-reports-css-fragment.html :: style"></th:block>
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 18 '21 at 16:06
0

For those who struggle with this, if you are using Spring Security dependency, make sure to give request to your folders like this:

enter image description here

Then put your CSS file under static/css folder.

And then add CSS link into your HTML file like this:

<link rel="stylesheet" th:href="@{/css/index.css}">

Hope this help someone!

covenant
  • 150
  • 1
  • 11