Sean Anderson
Sean Anderson

Reputation: 660

How do I repeat headers and footers fixed to top and bottom of document with CSS in iText 7 using pdfHTML converter?

When I use the following HTML+CSS to produce a table with header and footer it repeats the header and footer on each page when printing and when converting to PDF via iText 7 pdfHTML library. The document looks/behaves the same in both browser printing and in output as a PDF from iText. I'm trying to get the header and footer to be fixed to the top and bottom of each page regadless of the number of rows in the table.

Java/iText convert html document to pdf...

var mediaDeviceDescription = new MediaDeviceDescription(MediaType.PRINT);
var props = new ConverterProperties();
props.setMediaDeviceDescription(mediaDeviceDescription);
HtmlConverter.convertToPdf(html, new FileOutputStream(pdfTempFile.toFile()), props);  

The html...

<html>

<head>
    <title>Invoice</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        @page {
            margin: 0;
        }

        .label-font {
            font-size: 18px;
            letter-spacing: 3.6px;
            word-spacing: 2.2px;
            color: #000000;
            font-weight: 700;
            font-style: normal;
            text-transform: none;
        }

        .companySection {
            margin-top: 10px;
            width: 100%;
            display: flex;
            justify-content: space-between;
        }

        .logoBusinessDetails {
            flex-grow: 1;
        }

        .headerBusinessFont {
            font-size: 32px;
            letter-spacing: 3.6px;
            word-spacing: 2.2px;
            color: #000000;
            font-weight: 700;
            text-decoration: overline solid rgb(68, 68, 68);
            font-style: normal;
            font-variant: small-caps;
            text-transform: none;
        }

        table.invoiceItems {
            margin-top: 10px;
            border: 2px solid #000000;
            width: 100%;
            text-align: left;
            border-collapse: collapse;
        }

        table.invoiceItems td,
        table.invoiceItems th {
            border: 1px solid #000000;
            padding: 6px 5px;
        }

        table.invoiceItems tbody td {
            font-size: 13px;
        }

        table.invoiceItems tr:nth-child(even) {
            /*background: #667FFF;*/
        }

        table.invoiceItems thead {
            background: #667FFF;
            background: -moz-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
            background: -webkit-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
            background: linear-gradient(to bottom, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
            border-bottom: 3px solid #000000;
        }

        table.invoiceItems thead th {
            font-size: 15px;
            font-weight: bold;
            color: #000000;
            text-align: left;
        }

        invoiceItems.table.invoiceItems tfoot td {
            font-size: 14px;
        }

        .customerSection {
            width: 100%;
            display: flex;
            align-items: flex-start;
        }

        .billTo {
            border-left: 15px solid #808080;
            flex-grow: 1;
        }

        .shipTo {
            border-left: 15px solid #808080;
            flex-grow: 1;
        }

        .documentDetails {
            /*border-left: 15px solid #667FFF;*/
            flex-grow: 1;
        }



        .message {
            flex-basis: 65%
        }

        .totals {
            flex-basis: 35%;
        }

        .totalColumns {
            width: 100%;
            display: flex;
        }

        .totalLabels {
            flex-basis: 50%
        }

        .totalAmounts {
            flex-basis: 50%
        }

        /* Total labels column*/
        table.totalLabels {
            border-width: 0px 1px 0px 2px;
            border-style: solid;
            border-color: #000000;
            width: 100%;
            text-align: right;
            border-collapse: collapse;
        }

        table.totalLabels td,
        table.totalLabels th {
            border: 1px solid #000000;
            padding: 5px 4px;
        }

        table.totalLabels tbody td {
            font-size: 13px;
        }

        table.totalLabels tfoot {
            font-size: 14px;
            font-weight: bold;
            color: #000000;
            border-top: 3px solid #000000;
        }

        table.totalLabels tfoot td {
            font-size: 14px;
        }

        /* Amounts Column*/
        table.totalAmounts {
            border-width: 0px 2px 0px 1px;
            border-style: solid;
            border-color: #000000;
            width: 100%;
            text-align: right;
            border-collapse: collapse;
        }

        table.totalAmounts td,
        table.totalAmounts th {
            border: 1px solid #000000;
            padding: 5px 4px;
        }

        table.totalAmounts tbody td {
            font-size: 13px;
        }

        table.totalAmounts tfoot {
            font-size: 14px;
            font-weight: bold;
            color: #000000;
            border-top: 3px solid #000000;
        }

        table.totalAmounts tfoot td {
            font-size: 14px;
        }

        table.documentInfo {
            width: 100%;
            text-align: right;
        }

        table.documentInfo td,
        table.documentInfo th {}

        .footer {
            border: 2px #000000;
        }

        tbody.invoiceItems tr td {
            border: 1px solid #000000;
            text-align: left;
            border-collapse: collapse;
            width: 100%;

        }

        table {
            border-spacing: 0px;
            width: 100%;
        }

      
@media print, screen{
        .headerSection {
            width: 100%;
   
        }

        .summarySection {
            width: 100%;
            display: flex;
            align-items: flex-start;
        }

 
        }
}
    </style>

</head>

<body>


    <div>


        <table>

            <thead>

                <tr>

                    <td colspan="8">

                        <!--<div class="header-space"></div>-->
                        
                        <div class="headerSection">
        <div class="companySection">

            <div class="headerBusinessLogo headerBusinessFont  logoBusinessDetails">
                Acme Paper Co LLC
            </div>

        </div>



        <div class="customerSection">
            <div class="billTo"><span style="font-weight: bold;">Bill To:</span><br>${billTo}</div>

            <div class="shipTo"><span style="font-weight: bold;">Ship To:</span><br>${shipTo}</div>

            <div class="documentDetails">
                <table class="documentInfo">
                    <tbody>
                        <tr>
                            <td>Invoice Number</td>
                            <td>${invoice.invoiceNumber}</td>
                        </tr>
                        <tr>
                            <td>Invoice Date: </td>
                            <td>2022-03-12</td>
                        </tr>
                        <tr>
                            <td>Due Date</td>
                            <td>2022-03-12</td>
                        </tr>
                        <tr>
                            <td>Tax Id</td>
                            <td>D288658797</td>
                        </tr>
                    </tbody>
                </table>

            </div>
        </div>

    </div>

                    </td>
                </tr>

                <tr>
                    <th>Quantity</th>
                    <th>Code</th>
                    <th>Description</th>
                    <th>Unit Price</th>
                    <th>Tax1</th>
                    <th>Tax2</th>
                    <th>Line Total</th>
                </tr>
            </thead>

            <tbody class="invoiceItems">
                <!-- <#list items as item> -->
                <tr>
                    <td style="width: 10%;">${item.quantity}</td>
                    <td style="width: 15%;">
                        <#if item.code?has_content>
                            ${item.code}
                        </#if>
                    </td>
                    <td style="width: 45%;">${item.description}</td>
                    <td style="text-align: right; width: 10%;">${item.unitPrice}</td>
                    <td style="text-align: center; width: 5%;">${item.taxable1?string('yes','no')}</td>
                    <td style="text-align: center; width: 5%;">${item.taxable2?string('yes','no')}</td>
                    <td style="text-align: right; width: 10%;">${item.getItemSubtotal()}</td>

                </tr>

                <!-- other rows ommitted to reduce body size for SO-->                    
                <!-- </#list> -->

            </tbody>

            <tfoot>

                <tr>

                    <td colspan="8">

                        <!--<div class="summary-space"></div>-->
                         <div class="summarySection">
        <div class="message">
            <p>The 13-letter motto was suggested in 1776 by Pierre Eugene du Simitiere to the
                committee responsible for
                developing the seal. At the time of the American Revolution, the phrase appeared
                regularly on the title page
                of the London-based Gentleman's Magazine, founded in 1731,[10][11] which collected
                articles from many
                sources into one periodical. This usage in turn can be traced back to the
                London-based Huguenot Peter
                Anthony Motteux, who had employed the adage for his The Gentleman's Journal, or the
                Monthly Miscellany
                (1692-1694). The phrase is similar to a Latin translation of a variation of
                Heraclitus's tenth fragment,
                "The one is made up of all things, and all things issue from the one".</p>
        </div>
        <div class="totals">
            <div class="totalColumns">

                <table class="totalLabels">
                    <tbody>
                        <tr>
                            <td>Item Total</td>
                        </tr>
                        <tr>
                            <td>Discounts</td>
                        </tr>
                        <tr>
                            <td>Taxable1</td>
                        </tr>
                        <tr>
                            <td>Taxable2</td>
                        </tr>
                        <tr>
                            <td>Tax1</td>
                        </tr>
                        <tr>
                            <td>Tax2</td>
                        </tr>
                        <tr>
                            <td>Payments</td>
                        </tr>
                        <tr>
                            <td>Amount Due</td>
                        </tr>
                    </tbody>
                </table>

                <table class="totalAmounts">
                    <tbody>
                        <tr>
                            <td>${subTotal}</td>
                        </tr>
                        <tr>
                            <td>${discountsTotal}</td>
                        </tr>
                        <tr>
                            <td>${taxable1Subtotal}</td>
                        </tr>
                        <tr>
                            <td>${taxable2Subtotal}</td>
                        </tr>
                        <tr>
                            <td>${tax1Total}</td>
                        </tr>
                        <tr>
                            <td>${tax2Total}</td>
                        </tr>
                        <tr>
                            <td>${payments}</td>
                        </tr>
                        <tr>
                            <td>${amountDue}</td>
                        </tr>
                    </tbody>
                </table>

            </div>
        </div>

                    </td>

                </tr>
    </div>



    </tfoot>

    </table>

    </div>

</body>

</html>

Browser print output. (Notice how the footer (marked in red) appears directly after the end of the table content on the last page--or first page if there are not enough rows to fill an entire page.)

enter image description here

iText PDF Output. (Same behavior as printing the HTML version from the browser. The consistency is good here, but I want the footer to be pegged to the bottom of each page regardless of the number of table rows.)

enter image description here

I found a way to get the header and footer to be stuck to the top and bottom of each page regardless of the table row count using CSS, and it works when printing from the browser but iText is a disaster.

Using this method detailed here: https://medium.com/@Idan_Co/the-ultimate-print-html-template-with-header-footer-568f415f6d2a

This HTML+CSS...

<html>

<head>
    <title>Invoice</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        @page {
            margin: 0;
        }

        .label-font {
            font-size: 18px;
            letter-spacing: 3.6px;
            word-spacing: 2.2px;
            color: #000000;
            font-weight: 700;
            font-style: normal;
            text-transform: none;
        }

        .companySection {
            margin-top: 10px;
            width: 100%;
            display: flex;
            justify-content: space-between;
        }

        .logoBusinessDetails {
            flex-grow: 1;
        }

        .headerBusinessFont {
            font-size: 32px;
            letter-spacing: 3.6px;
            word-spacing: 2.2px;
            color: #000000;
            font-weight: 700;
            text-decoration: overline solid rgb(68, 68, 68);
            font-style: normal;
            font-variant: small-caps;
            text-transform: none;
        }

        table.invoiceItems {
            margin-top: 10px;
            border: 2px solid #000000;
            width: 100%;
            text-align: left;
            border-collapse: collapse;
        }

        table.invoiceItems td,
        table.invoiceItems th {
            border: 1px solid #000000;
            padding: 6px 5px;
        }

        table.invoiceItems tbody td {
            font-size: 13px;
        }

        table.invoiceItems tr:nth-child(even) {
            /*background: #667FFF;*/
        }

        table.invoiceItems thead {
            background: #667FFF;
            background: -moz-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
            background: -webkit-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
            background: linear-gradient(to bottom, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
            border-bottom: 3px solid #000000;
        }

        table.invoiceItems thead th {
            font-size: 15px;
            font-weight: bold;
            color: #000000;
            text-align: left;
        }

        invoiceItems.table.invoiceItems tfoot td {
            font-size: 14px;
        }

        .customerSection {
            width: 100%;
            display: flex;
            align-items: flex-start;
        }

        .billTo {
            border-left: 15px solid #808080;
            flex-grow: 1;
        }

        .shipTo {
            border-left: 15px solid #808080;
            flex-grow: 1;
        }

        .documentDetails {
            /*border-left: 15px solid #667FFF;*/
            flex-grow: 1;
        }



        .message {
            flex-basis: 65%
        }

        .totals {
            flex-basis: 35%;
        }

        .totalColumns {
            width: 100%;
            display: flex;
        }

        .totalLabels {
            flex-basis: 50%
        }

        .totalAmounts {
            flex-basis: 50%
        }

        /* Total labels column*/
        table.totalLabels {
            border-width: 0px 1px 0px 2px;
            border-style: solid;
            border-color: #000000;
            width: 100%;
            text-align: right;
            border-collapse: collapse;
        }

        table.totalLabels td,
        table.totalLabels th {
            border: 1px solid #000000;
            padding: 5px 4px;
        }

        table.totalLabels tbody td {
            font-size: 13px;
        }

        table.totalLabels tfoot {
            font-size: 14px;
            font-weight: bold;
            color: #000000;
            border-top: 3px solid #000000;
        }

        table.totalLabels tfoot td {
            font-size: 14px;
        }

        /* Amounts Column*/
        table.totalAmounts {
            border-width: 0px 2px 0px 1px;
            border-style: solid;
            border-color: #000000;
            width: 100%;
            text-align: right;
            border-collapse: collapse;
        }

        table.totalAmounts td,
        table.totalAmounts th {
            border: 1px solid #000000;
            padding: 5px 4px;
        }

        table.totalAmounts tbody td {
            font-size: 13px;
        }

        table.totalAmounts tfoot {
            font-size: 14px;
            font-weight: bold;
            color: #000000;
            border-top: 3px solid #000000;
        }

        table.totalAmounts tfoot td {
            font-size: 14px;
        }

        table.documentInfo {
            width: 100%;
            text-align: right;
        }

        table.documentInfo td,
        table.documentInfo th {}

        .footer {
            border: 2px #000000;
        }

        tbody.invoiceItems tr td {
            border: 1px solid #000000;
            text-align: left;
            border-collapse: collapse;
            width: 100%;

        }

        table {
            border-spacing: 0px;
            width: 100%;
        }

      
@media print, screen{
        .headerSection {
            width: 100%;
            position: fixed;
            top: 0;
        }

        .summarySection {
            width: 100%;
            display: flex;
            align-items: flex-start;
        }

        .headerSection,
        .header-space {
            height: 200px;
        }

        .summarySection,
        .summary-space {
            height: 230px;
        }

        .summarySection {
            position: fixed;
            bottom: 0;
        }
}
    </style>

</head>

<body>


    <div>


        <table>

            <thead>

                <tr>

                    <td colspan="8">

                        <div class="header-space"></div>

                    </td>
                </tr>

                <tr>
                    <th>Quantity</th>
                    <th>Code</th>
                    <th>Description</th>
                    <th>Unit Price</th>
                    <th>Tax1</th>
                    <th>Tax2</th>
                    <th>Line Total</th>
                </tr>
            </thead>

            <tbody class="invoiceItems">
                <!-- <#list items as item> -->
                <tr>
                    <td style="width: 10%;">${item.quantity}</td>
                    <td style="width: 15%;">
                        <#if item.code?has_content>
                            ${item.code}
                        </#if>
                    </td>
                    <td style="width: 45%;">${item.description}</td>
                    <td style="text-align: right; width: 10%;">${item.unitPrice}</td>
                    <td style="text-align: center; width: 5%;">${item.taxable1?string('yes','no')}</td>
                    <td style="text-align: center; width: 5%;">${item.taxable2?string('yes','no')}</td>
                    <td style="text-align: right; width: 10%;">${item.getItemSubtotal()}</td>

                </tr>

                <!-- other rows ommitted to reduce body size for SO-->                     
                
                <!-- </#list> -->

            </tbody>

            <tfoot>

                <tr>

                    <td colspan="8">

                        <div class="summary-space"></div>


                    </td>

                </tr>
    </div>



    </tfoot>

    </table>


    <div class="headerSection">
        <div class="companySection">

            <div class="headerBusinessLogo headerBusinessFont  logoBusinessDetails">
                ACME Gaming Co LLC
            </div>

        </div>



        <div class="customerSection">
            <div class="billTo"><span style="font-weight: bold;">Bill To:</span><br>${billTo}</div>

            <div class="shipTo"><span style="font-weight: bold;">Ship To:</span><br>${shipTo}</div>

            <div class="documentDetails">
                <table class="documentInfo">
                    <tbody>
                        <tr>
                            <td>Invoice Number</td>
                            <td>${invoice.invoiceNumber}</td>
                        </tr>
                        <tr>
                            <td>Invoice Date: </td>
                            <td>2022-03-12</td>
                        </tr>
                        <tr>
                            <td>Due Date</td>
                            <td>2022-03-12</td>
                        </tr>
                        <tr>
                            <td>Tax Id</td>
                            <td>D288658797</td>
                        </tr>
                    </tbody>
                </table>

            </div>
        </div>

    </div>

    <div class="summarySection">
        <div class="message">
            <p>The 13-letter motto was suggested in 1776 by Pierre Eugene du Simitiere to the
                committee responsible for
                developing the seal. At the time of the American Revolution, the phrase appeared
                regularly on the title page
                of the London-based Gentleman's Magazine, founded in 1731,[10][11] which collected
                articles from many
                sources into one periodical. This usage in turn can be traced back to the
                London-based Huguenot Peter
                Anthony Motteux, who had employed the adage for his The Gentleman's Journal, or the
                Monthly Miscellany
                (1692-1694). The phrase is similar to a Latin translation of a variation of
                Heraclitus's tenth fragment,
                "The one is made up of all things, and all things issue from the one".</p>
        </div>
        <div class="totals">
            <div class="totalColumns">

                <table class="totalLabels">
                    <tbody>
                        <tr>
                            <td>Item Total</td>
                        </tr>
                        <tr>
                            <td>Discounts</td>
                        </tr>
                        <tr>
                            <td>Taxable1</td>
                        </tr>
                        <tr>
                            <td>Taxable2</td>
                        </tr>
                        <tr>
                            <td>Tax1</td>
                        </tr>
                        <tr>
                            <td>Tax2</td>
                        </tr>
                        <tr>
                            <td>Payments</td>
                        </tr>
                        <tr>
                            <td>Amount Due</td>
                        </tr>
                    </tbody>
                </table>

                <table class="totalAmounts">
                    <tbody>
                        <tr>
                            <td>${subTotal}</td>
                        </tr>
                        <tr>
                            <td>${discountsTotal}</td>
                        </tr>
                        <tr>
                            <td>${taxable1Subtotal}</td>
                        </tr>
                        <tr>
                            <td>${taxable2Subtotal}</td>
                        </tr>
                        <tr>
                            <td>${tax1Total}</td>
                        </tr>
                        <tr>
                            <td>${tax2Total}</td>
                        </tr>
                        <tr>
                            <td>${payments}</td>
                        </tr>
                        <tr>
                            <td>${amountDue}</td>
                        </tr>
                    </tbody>
                </table>

            </div>
        </div>



        <!--<div class="footer" style="border: 2px solid black;">Page 1 of 1</div>-->

    </div>

</body>






</html>

The browser print output... (works perfect...header/footer at the top/ bottom of every page)

enter image description here

iText pdfHTML output... (doesn't print header/footer until the end...just ignores fixed position CSS and dumps the header and footer div after the table content)

enter image description here

Upvotes: 0

Views: 2956

Answers (1)

Alexey Subach
Alexey Subach

Reputation: 12312

pdfHTML 4.0.2 allows you to make a PDF from an HTML file and add headers and footers easily in a completely declarative style (so only using HTML+CSS combination without necessity to write a lot of boilerplate code).

And you can even add page numbers to your output PDF, all with pure CSS instructions that are processed by pdfHTML.

Here is an example of an HTML file:

<!DOCTYPE html>
<html>
<body>

<style>
  #header {
    position: running(header);
  }

  #footer {
    position: running(footer);
  }

  @page {
    margin-top: 100px;
    margin-bottom: 100px;

    @top-center {
      content: element(header);
    }

    @bottom-center {
      content: element(footer);
    }
  }

  #current-page-placeholder::before {
    content: counter(page);
  }


  #total-pages-placeholder::before {
    content: counter(pages);
  }
</style>

<div id="header"><p style="color: green">This is a header</p></div>
<div id="footer"><p style="color: red">This is a footer. Page <span id="current-page-placeholder"></span>/<span id="total-pages-placeholder"></span></p></div>

<h2>An ordered HTML list</h2>

<ol>
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ol>

<ol type="1">
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ol>

<ol type="A">
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ol>

<ol type="a">
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ol>

<ol type="I">
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ol>

<ol type="i">
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ol>

</body>
</html>

Visual output result:

result

And that is achieved by a single line call:

HtmlConverter.convertToPdf(new File("in.html"), new File("out.pdf"));

Upvotes: 3

Related Questions