Reputation: 660
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.)
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.)
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)
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)
Upvotes: 0
Views: 2956
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:
And that is achieved by a single line call:
HtmlConverter.convertToPdf(new File("in.html"), new File("out.pdf"));
Upvotes: 3