HTML Templates for new Velocity PDF (iText) Reports - A Quick Start Guide

Katie
Katie
  • Updated

Author: Katie Huckett

Updated: November 2024

Audience: Velocity: HTML Templates for new Velocity PDF (iText) Reports

Products Applicable: Jama Connect®

Use Case

HTML Templates for new Velocity PDF (iText) Reports - A Quick Start Guide

Best Practice

We are now using an iTextPdf library to produce PDF reports from Velocity templates. This library has more features, is easier to use, and does not have the performance issues we experienced with our current solution, AsposePDF.

The new implementation simplifies the templates in many ways. There's no need to use custom header/footer tags, and the new library interprets HTML style parameters correctly, so all the formatting, margin control, page decorations, etc., can be specified in vanilla HTML and CSS styles.

This quick-start document explains the basics of writing HTML templates for Velocity PDFs using the new library.

Basic HTML Templates

You can use plain HTML with a <body> section to create a basic template. The fastest and easiest way to check the appearance and layout is by opening the HTML file in a browser. However, remember that any embedded Velocity code will not render, there will be no page breaks, and the headers/footers included will not render. The report must be uploaded and run in Connect to see all the PDF features. Also, remember that when uploading these HTML templates to Connect, they must have a .vm extension.
Here is a simple template for reporting on all the item names in the current project:

<html>
<head>
    <title>All Item Names</title> 
</head>

<body>
    Project: $project.name
    <ul>
        #foreach( $itemId in $documentSource.getActiveDocumentIdsInProject($project.id) )
           #set ($item = $documentSource.getDocument($itemId))
           <li>$item.name</li>
        #end
    </ul>
</body>
</html>

Styling

CSS styles are applied as usual. These styles should be honored in the PDF, but if problems arise with how a particular style is rendered in the PDF, please let the Quick Wins team know so we can investigate.

This template is the exact item name report, but with added margins, specified background color and font, and a green check mark before each item name.

<html>
<head>
<style>        
  .item-list li {
      position: relative;
      list-style-type: none;
      padding-left: 2.5rem;
      margin-bottom: 0.5rem;
  }
  .item-list li:before {
     content: '';
     display: block;
     position: absolute;
     left: 0;
     top: -2px;
     width: 5px;
     height: 11px;
     border-width: 0 2px 2px 0;
     border-style: solid;
     border-color: #00a8a8;
     transform-origin: bottom left;
     transform: rotate(45deg);
   } 
   html {
      -webkit-font-smoothing: antialiased;
      font-family: "Helvetica Neue", sans-serif;
      font-size: 62.5%;
   }    
   body {
      width: 790px;
      font-size: 1.6rem; /* 18px */
      background-color: #efefef;
      color: #324047
   }
   html, body, section {
      height: 100%;
   }
   section {
      max-width: 400px;
      margin-left: 10px;
      margin-right: 20px;
      display: flex;
      align-items: center;
   }
   div {
      margin: auto;
   }
</style>
    <title>All Item Names</title>
</head>

<body>
<section>
    <div>
        <h2>Items for Project $project.name</h2>
        <ul class="item-list">
            #foreach( $itemId in
                $documentSource.getActiveDocumentIdsInProject($project.id)
            )
                #set ($item =
                    $documentSource.getDocument($itemId))
                <li>$item.name</li>
            #end
        </ul>
    </div>
</section>
</body>
</html>

Setting Paged Media Attributes

The CSS rule @page allows report writers to specify page layout details such as margins, size, orientation, headers, footers, and page numbering.

For instance, by including the following CSS rules in the style section, you can adjust the page size to 8.5 by 11 inches, set all margins to 2cm, introduce a top margin of 10cm specifically for the first page, and prevent page breaks from happening immediately after an <h2> element.

<style>
    
    @page {
        size: 8.5in 11in;
        margin: 2cm;
    }

    @page :first {
        margin-top: 10cm;
    }

    h2 { page-break-after : avoid }

</style>

By specifying the page dimensions and including the "landscape" keyword, reports can be generated with a landscape orientation.

<style>
        @page {
            size: A4 landscape;

            @bottom-right {
                font-family: Arial, Helvetica, sans-serif;
                font-size: 10.0pt;
                content: "Page " counter(page) " of " counter(pages);
            }
        }
</style>

Refer to these online resources for a comprehensive description of the available page formatting.

Adding Headers and Footers

The @page rule can include custom headers and footers on every page or selected page. The process is:

  1. Define an element in the HTML body section with a class name uniquely identified as a header or footer. This element defines the look of the header or footer.
  2. Include the CSS for the header/footer class in the style section. This style definition must include the position: running(...) attribute.
  3. In the @page rule block, include positioning information for the header/footer.

Here is an implementation example for adding a simple header and footer:

1 Define header/footer HTML elements

In this simple example, these are just text elements that appear on the page. A more complex example is in the next section. 

<div class='header'>An Awesome Report</div><div 
class='footer'>Copyright 2021, Jama Software</div>

To properly define the header and footer, the <div> elements should be placed at the top of the <body> section immediately after the <body> tag. The class names for the header and footer elements do not necessarily have to be "header" or "footer" but must be unique.

2 Include CSS for headers and footers 

div.header {
   display: block; text-align: center;
   position: running(header);
}

div.footer {
   font-family: Courier, sans-serif;
   font-size: 10.0pt;
   display: block; text-align: center;
   position: running(footer);
}

The styles for the header/footer HTML elements are defined here. The essential elements in these styles are the position properties. They must be set to running with the class name specified for the header/footer. Running indicates to the layout system that the class is removed from the normal page layout flow and can be referenced in the @page block.

3 Define the header/footer in the @page block

In the @page rule block described above, set the positioning for the header/footer and declare the content that will appear in this position. There are 16 possible position attributes, but the most likely to be used for headers/footers are @top-left, @top-center, @top-right, @bottom-left, @bottom-center, and @bottom-right. The content:element attributes must reference the class name for the header/footer. 

@page {
   size: A4;
   @top-center { content: element(header) }
   @bottom-center { content: element(footer) }
}

Below is a more complex footer with an image, dynamic content, and page info. It follows the same pattern but includes a couple of special techniques.
This HTML describes a footer that includes an image, static text, dynamic data fetched from Velocity, and paging info:

<div class="footer">
    <table class="footer-table">
        <tr>
            <td rowspan="2">
                <img height="30" width="30" src="https://www.jamasoftware.com/media/gravatar/jama-avatar.png"
                     alt="Jama Logo">
            </td>
            <td>
                Confidential: Jama Software
            </td>
            <td rowspan="2">
                Page <span id="pageNumber"></span> of <span id="totalPages"></span>
            </td>
        </tr>
        <tr>
            <td>Report generated on: $dateTool.getSystemDate()<br>By: $userSource.currentUser.fullName</td>
        </tr>
    </table>
</div>

When rendered in the report, it looks like this: 

Screenshot 2024-08-16 at 3.24.57 PM.png

The only unusual element here is the display of the page numbers. Two components are defined in the <style> section and used on line 12: pageNumber and totalPages.

The components for page numbering are defined in the <style> section as follows:

#pageNumber::after {
   content: counter(page)
}

#totalPages::after {
   content: counter(pages)
}

This code will display the default page number after any HTML element with the id "pageNumber" and the total page count after any HTML element with the "totalPages" id. The page numbers use the built-in CSS page counters counter(page) and counter(pages) for styling.

Images

Images in Velocity PDFs come from two sources:

  • Images that are part of an item get stored in Connect (e.g., Wiris equations or images uploaded into the rich text areas).
  • External images

External images are displayed using the <img> tag. The src attribute should point to a location accessible by Connect when generating the report.

The library we use for rendering PDFs only partially supports all SVGs. To ensure that all SVGs included in a report are rendered correctly, Connect automatically converts them to PNGs before they are included in the report. When the third-party library fully supports rendering for all SVGs, we can render them in reports without conversion.

Tables

  • Centered and appropriately aligned tables in PDFs rely on CSS for proper alignment. It's essential to define the width of a table as a percentage rather than in pt or px; for example, width: 80%.
  • Word breaks must also be managed at the TableCell and TableHeader levels, utilizing overflow wrap.

Images Controlling Page Size and Margins

Page size is specified in the @page rule block using the size element, as seen above. Several preset values are available, but custom widths and heights may also be set.

Margins in HTML are adjusted using CSS values. For instance, this CSS code will reduce the margins for a text-only section of the report:

.intro-text {
   margin-left: 100px;
   margin-right:200px;
}

Default Fonts vs Embedded Fonts 

The iText pdfHTML add-on, by default, supports these 14 Standard Type 1 fonts:

  • Times-Roman, Helvetica, Courier, Symbol, Times-Bold, Helvetica-Bold, Courier-Bold, ZapfDingbats, Times-Italic, Helvetica-Oblique, Courier-Oblique, Times-BoldItalic, Helvetica-BoldOblique, Courier-BoldOblique
  • These fonts can be referenced in the HTML without needing CSS styling definitions or external links.

To include fonts other than the default Connect system fonts, you can embed them in the HTML template. Here's an example of how to embed and use a font in the <style> section of the HTML template. Like external images, the font's URL must point to a location that Connect can access locally when running the report.

@font-face {
   font-family: 'rakkasregular';
   src: url('/Users/krichards/Desktop/Rakkas/Rakkas-Regular.ttf') format('ttf'),
   url('/Users/krichards/Desktop/Rakkas/Rakkas-Regular.ttf') format('ttf');
   font-weight: normal;
   font-style: normal;
}

.intro-text {
   margin-left: 100px;
   margin-right:200px;
   font-family: 'rakkasregular';
}

When creating a report in Connect that requires remotely accessible fonts, use a <link> element to specify the URL of the remote font. This <link> element should be placed within the <head> element of the template and should not mix with the <style> element. The format of the <link> element should follow these guidelines:

<link rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Tangerine">

The family name can be used as the value for any other font-family attributes in the template.

Note: The CSS @import rule is not compatible with iText pdf HTML

Compiled Example template

All the techniques discussed in this document are in this complete Velocity template:

<html>
<head>
    <style>

        @page {
            size: A4;
            @top-center {
                content: element(header)
            }
            @bottom-center {
                content: element(footer)
            }
        }

        #pageNumber::after {
            content: counter(page)
        }

        #totalPages::after {
            content: counter(pages)
        }


        div.header {
            display: block;
            text-align: center;
            position: running(header);
        }

        div.footer {
            font-family: Arial, Helvetica, sans-serif;
            font-size: 8.0pt;
            display: block;
            text-align: center;
            position: running(footer);
        }

        .item-list li {
            position: relative;
            list-style-type: none;
            padding-left: 2.5rem;
            margin-bottom: 0.5rem;
        }

        .item-list li:before {
            content: '';
            display: block;
            position: absolute;
            left: 0;
            top: -2px;
            width: 5px;
            height: 11px;
            border-width: 0 2px 2px 0;
            border-style: solid;
            border-color: #00a8a8;
            transform-origin: bottom left;
            transform: rotate(45deg);
        }

        html {
            -webkit-font-smoothing: antialiased;
            font-family: "Helvetica Neue", sans-serif;
            font-size: 62.5%;
        }

        body {
            width: 790px;
            font-size: 1.6rem; /* 18px */
            background-color: #efefef;
            color: #324047
        }

        html,
        body,
        section {
            height: 100%;
        }

        section {
            max-width: 400px;
            margin-left: 10px;
            margin-right: 20px;
            display: flex;
            align-items: center;
        }

        div {
            margin: auto;
        }

        .footer-table  {
            width: 100%;
            height: 100px;
            vertical-align: middle;
            horiz-align: center;
            margin-bottom: 5px;
        }

        @font-face {
            font-family: 'rakkasregular';
            src: url('/Users/krichards/Desktop/Rakkas/Rakkas-Regular.ttf') format('ttf'),
            url('/Users/krichards/Desktop/Rakkas/Rakkas-Regular.ttf') format('ttf');
            font-weight: normal;
            font-style: normal;
        }

        .report-info {
            margin-left: 150px;
            margin-right: 100px;
            font-family: 'rakkasregular';
            background-color: white;
            page-break-after: always;
        }

    </style>

    <title>All Item Names</title>
</head>

<body>
<div class="header">All Item Names in the Project</div>
<div class="footer">
    <table class="footer-table">
        <tr>
            <td rowspan="2">
                <img height="30" width="30" src="https://www.jamasoftware.com/media/gravatar/jama-avatar.png"
                     alt="Jama Logo">
            </td>
            <td>
                Confidential: Jama Software
            </td>
            <td rowspan="2">
                Page <span id="pageNumber"></span> of <span id="totalPages"></span>
            </td>
        </tr>
        <tr>
            <td>Report generated on: $dateTool.getSystemDate()<br>By: $userSource.currentUser.fullName</td>
        </tr>
    </table>
</div>
<h2>Summary of Report</h2>
<div class="report-info">
    <p>Text goes here</p>
</div>
<section>
    <div>
        <h2>Items for Project $project.name</h2>
        <ul class="item-list">
            #foreach( $itemId in
                $documentSource.getActiveDocumentIdsInProject($project.id)
            )
                #set ($item =
                    $documentSource.getDocument($itemId))
                <li>$item.name</li>
            #end
        </ul>
    </div>
</section>
</body>
</html>

Troubleshooting

Spacing Inconsistencies

When the iText pdfHTML add-on is generating the final PDF document, the spacing determined between lines separated by <br /> tags within one <p> element vs lines separated by two distinct <p> elements will be unequal. The <br /> tags result in less space than entirely separate <p> elements. This should be kept in mind when writing the template itself, but it should also be kept in mind when thinking about the rich text fields in the Connect items themselves. These rich text fields are represented by underlying HTML that will get piped into the final HTML output from Velocity when running your template. If the rich text field contains lines of data separated by complete -enter- breaks, they will appear with more space in the final PDF than lines separated by -shift+enter- breaks. This is because pressing Enter creates new <p> elements, while Shift+Enter inserts <br /> tags without terminating the current <p> element.

Escaped Hexadecimal Failure 

If you want to represent a character using its hexadecimal value equivalent (e.g., \0000a0 for &nbsp;), it is essential to remember that any characters following the hexadecimal value can sometimes be mistakenly interpreted as part of the same value if there is no whitespace between them. However, if not desired for the final display, one may be opposed to separating them via whitespace. To assist with this problem, CSS allows for one single whitespace character following an escaped hexadecimal value to be used as a separator that it will ignore to display. This usually wouldn’t be required for back-to-back escaped hexadecimal values (e.g., \0000a0\0000a0), but it does seem to cause issues with iText by not treating the second as escaped. We recommend using the free trailing whitespace character as a separator when writing escaped hexadecimal values to ensure they are correctly escaped (\0000a0 \0000a0).

References

Please feel free to leave feedback in the comments below.

Related to

Was this article helpful?

0 out of 0 found this helpful

Have more questions? Submit a request

Comments

0 comments

Please sign in to leave a comment.