I'm developing an asp.net mvc project and I'm using an html template to generate an invoice document with dynamic data which I filled up thanks to handlebars. The html resulting is being converted to PDF by using iText 7, since is the tool required by the company for using to, however I'm facing an issue when converting that to a PDF because of an html table that shows data, some times has enough data to fit just into one page, but in other cases, there are many rows that meet into a page break, printing the data across both pages. I need to move the whole table block to the next page whenever the data doesn't fit in one page.
Here is the template (Mytemplate.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
<!-- Removed inline styles for space reasons -->
</style>
</head>
<body>
<div style="width: 100%; margin: 0 auto; text-align: center; display: flex; justify-content: center;">
<div style="max-width: 700px; background-color: white; width: 100%">
<table style="width:100%">
<tbody>
<tr>
<td>
<table class="table-header table-information m-0 border-zero">
<tr>
<td class="text-al" style="vertical-align: top">
<img src="{{baseUrl}}\images\clear_purple.png" style="width: 150px; height: 50px;">
</td>
<td class="text-al" style="vertical-align: top">
<table class="table-information m-0 border-zero">
<tbody>
<tr>
<td class="td-information p-0">
<p class="text p-0">Insured: {{insured}}</p>
</td>
</tr>
<tr>
<td class="td-information p-0">
<p class="text p-0">{{facilityName}}</p>
</td>
</tr>
</tbody>
</table>
</td>
<td class="text-al vertical-ab">
<table class="table-information m-0 border-zero">
<tbody>
<tr>
<td class="td-information p-0">
<p class="text p-0">Invoice Period: {{invoicePeriod}}</p>
</td>
</tr>
<tr>
<td class="td-information p-0">
<p class="text p-0">Date sent to LCTI: {{invoiceDateLTCI}}</p>
</td>
</tr>
<tr>
<td class="td-information p-0">
<p class="text p-0">LTCI Rep: {{clientReferrer}}</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="table-information">
<tbody>
<tr class="tr-information">
<th class="td-background" colspan="3">
<p class="text-title">Residency Information</p>
</th>
</tr>
<tr>
<td class="td-information p-0 vertical-ab" colspan="3">
<table class="table-information border-zero mb-0"">
<tbody>
<tr>
<td class="td-information w-35">
<p class="text text-bold">Facility Type</p>
<p class="text text-bold">Room/Unit Number</p>
<p class="text text-bold">Insurance Provider</p>
<p class="text text-bold">Insured’s Policy Number</p>
<p class="text text-bold">Invoice Number</p>
</td>
<td class="td-information w-65">
<p class="text-light">{{residencyInfo.facilityType}}</p>
<p class="text-light">{{residencyInfo.roomNumberChangeTo}}{{#if residencyInfo.roomNumberChange}} - {{residencyInfo.roomNumberChangeReason}}{{/if}}</p>
<p class="text-light">{{residencyInfo.insurerProvider}}</p>
<p class="text-light">{{residencyInfo.policyNumber}}</p>
<p class="text-light">{{residencyInfo.invoiceNumber}}</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td class="td-information td-border-top br-1 w-45">
<table class="w-full">
<tbody>
{{#if residencyInfo.haveDaysOutOfFacility}}
<tr>
<td>
<p class="text text-bold">Total Days in Facility</p>
</td>
<td>
<p class="text-light">{{residencyInfo.totalDaysInFacility}}</p>
</td>
</tr>
<tr>
<td>
<p class="text text-bold">Total Days out of Facility</p>
</td>
<td>
<p class="text-light">{{residencyInfo.totalDaysOutOfFacility}}</p>
</td>
</tr>
{{/if}}
{{#if residencyInfo.haveBedHoldDays}}
<tr>
<td>
<p class="text text-bold">Bedhold Days</p>
</td>
<td>
<p class="text-light">{{residencyInfo.bedHoldDays}}</p>
</td>
</tr>
{{/if}}
</tbody>
</table>
</td>
<td class="td-information bl td-border-top">
<table class="w-full">
<tbody>
<tr>
<td class="td-information-width">
<p class="text-light-2">Reason for Absence</p>
</td>
<td class="td-information-width">
<p class="text-light-2 text-ar">Dates of Absence</p>
</td>
</tr>
{{#each residencyInfo.absenceReasons}}
<tr>
<td>
<p class="text-light">{{reason}}</p>
</td>
<td>
<p class="text-light text-ar">{{from}}th{{#if hasTo}} - {{to}}th{{/if}}</p>
</td>
</tr>
{{/each}}
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
<table class="table-information">
<tbody>
<tr class="tr-information">
<th class="td-background" colspan="3">
<p class="text-title">Care Provided</p>
</th>
</tr>
<tr>
<td class="td-information p-0" style="width: 50%;">
<table class="table-information border-zero mb-0">
<tbody>
<tr>
<th class="td-information w-58 th-activity td-border-bottom">
<p class="text text-bold">Activities of Daily Living (ADLS)</p>
</th>
<th class="td-information p-0 w-14 td-border-bottom vertical-ab">
<div class="rotate-315">
<p class="text-s10 text-ac text-gray-500">Standby</p>
</div>
</th>
<th class="td-information p-0 w-14 td-border-bottom vertical-ab">
<div class="rotate-315">
<p class="text-s10 text-ac text-gray-500">Hands On</p>
</div>
</th>
<th class="td-information p-0 w-14 td-border-bottom vertical-ab">
<div class="rotate-315">
<p class="text-s10 text-ac text-gray-500">Provided</p>
</div>
</th>
</tr>
{{#each careProvided.adls}}
<tr>
<td class="td-information th-activity br-1 vertical-ac td-border-bottom">
<p class="text text-light">{{name}}</p>
</td>
<td class="td-information p-4 br-1 text-ac vertical-ac td-border-bottom">
{{#if standBy}}
<img src="{{../baseUrl}}\images\clear_check.png" style="width: 14px; height: 14px;">
{{/if}}
</td>
<td class="td-information p-4 br-1 text-ac vertical-ac td-border-bottom">
{{#if handsOn}}
<img src="{{../baseUrl}}\images\clear_check.png" style="width: 14px; height: 14px;">
{{/if}}
</td>
<td class="td-information p-4 br-1 text-ac vertical-ac td-border-bottom">
{{#if provided}}
<img src="{{../baseUrl}}\images\clear_check.png" style="width: 14px; height: 14px;">
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</td>
<td class="td-information p-0" rowspan="3" style="width: 50%; padding-top: 7px;">
<table class="table-information m-0 border-zero mb-0">
<tbody>
<tr>
<th class="td-information td-border-bottom">
<p class="text text-bold pl-10">Instrumental Activities of Daily Living (IADLS)</p>
</th>
</tr>
{{#each careProvided.iadls}}
<tr>
<td class="td-information pt-0 pb-0">
<p class="text text-light pl-10 ">{{name}}</p>
</td>
</tr>
{{/each}}
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
<table class="table-information">
<tbody>
<tr class="tr-information">
<th class="td-background" colspan="4">
<p class="text-title">Invoice Totals</p>
</th>
</tr>
<tr>
<td style="width: 58%;">
<table class="table-information border-zero m-0">
<tbody>
{{#each invoiceTotals.charges}}
<tr>
<td class="td-information pt-0-10" style="width:60%">
<p class="text text-bold" style="font-size:15px">{{name}}</p>
</td>
<td class="td-information pt-0-10" style="width:15%">
<p class="text text-light text-ar">{{amount}}</p>
</td>
<td class="td-information p-4" style="width:25%">
{{#if includedRnB}}
<p class="text-small">Included in R&B</p>
{{/if}}
</td>
</tr>
{{/each}}
<tr>
<td class="td-information text-ar" style="padding-right: 10px" colspan="2">
<p class="text-title-35 text-green-500 text-ar">${{invoiceTotals.totalAmount}}</p>
</td>
</tr>
</tbody>
</table>
</td>
<td class="td-information pt-0" rowspan="2" style="width: 48%;">
<table class="table-information p-0 m-0 border-zero">
<tbody>
<tr>
<th class="td-information pl-0 pt-0-10"><p class="text text-bold">Other Payment sources:</p></th>
</tr>
{{#each invoiceTotals.otherPaymentSources}}
<tr>
<td class="td-information pl-0 pt-0-10"><p class="text text-light">{{name}}</p></td>
</tr>
{{/each}}
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
<table class="table-information">
<tbody>
<tr class="tr-information">
<th class="td-background" style="width: 25%">
<p class="text-title text-al" style="font-size: 16px; padding-left: 10px">Reviewed By</p>
</th>
<th class="td-background" style="width: 35%">
<p class="text-title text-al" style="font-size: 16px; padding-left: 10px">Signed By</p>
</th>
<th class="td-background" style="width: 25%">
<p class="text-title text-al" style="font-size: 16px; padding-left: 10px">Signed On</p>
</th>
<th class="td-background" style="width: 15%">
<p class="text-title text-al" style="font-size: 16px; padding-left: 10px">Ip Address</p>
</th>
</tr>
<tr>
<td class="td-information pt-0-10" style="padding-top: 16px;"><p class="text pl-4" style="font-size: 15px">{{signature.facility.title}}</p></td>
<td class="td-information pt-0-10" style="padding-top: 16px;"><p class="text" style="font-family: BrushScript; font-size: 24px">{{signature.facility.signedBy}}</p></td>
<td class="td-information pt-0-10" style="padding-top: 16px;"><p class="text" style="font-size: 15px">{{signature.facility.signedOn}}</p></td>
<td class="td-information pt-0-10" style="padding-top: 16px;"><p class="text text-light" style="font-size: 15px">{{signature.facility.ipAddress}}</p></td>
</tr>
<tr>
<td class="td-information pt-0-10"><p class="text pl-4" style="font-size: 15px">{{signature.client.title}}</p></td>
<td class="td-information pt-0-10"><p class="text" style="font-size: 15px">{{signature.client.signedBy}}</p></td>
<td class="td-information pt-0-10"><p class="text" style="font-size: 15px">{{signature.client.signedOn}}</p></td>
<td class="td-information pt-0-10"><p class="text text-light" style="font-size: 15px">{{signature.client.ipAddress}}</p></td>
</tr>
<tr>
<td class="td-information pt-0-10"><p class="text pl-4" style="font-size: 15px">{{signature.local.title}}</p></td>
<td class="td-information pt-0-10"><p class="text" style="font-size: 15px">{{signature.local.signedBy}}</p></td>
<td class="td-information pt-0-10"><p class="text" style="font-size: 15px">{{signature.local.signedOn}}</p></td>
<td class="td-information pt-0-10"><p class="text text-light" style="font-size: 15px">{{signature.local.ipAddress}}</p></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
You can see markers such as {{insured}}, etc. and commands such as {{#each residencyInfo.absenceReasons}} for looping through data, all are required by handlebars for data replacing. Here part of the code:
string templateHtml = File.ReadAllText(System.IO.Path.Combine(basePath, "templates/MyTemplate.html"));
var template = Handlebars.Compile(templateHtml);
var data = new
{
baseUrl = basePath,
insured = invoice.CustomerStatus, // Customer Name
facilityName = invoice.FacilityName,
invoicePeriod = invoice.PeriodFrom.ToString("MMM yyyy"),
invoiceDateLTCI = DateTime.Now.ToString("MM/dd/yyyy"),
clientReferrer = invoice.ClientReferrerName,
residencyInfo = new
{
facilityType = currenttFacilityType,
insurerProvider = invoice.Insurer,
policyNumber = invoice.ClaimNumber,
... // removed intencionally
absenceReasons = JsonConvert.DeserializeAnonymousType(JsonConvert.SerializeObject(facilityAbsenceReasons), jsonFacilityAbsenceReasonsDefinition)
},
careProvided = new
{
adls = ..., // removed intencionally
iadls = ... // removed intencionally
},
invoiceTotals = new
{
charges = JsonConvert.DeserializeAnonymousType(JsonConvert.SerializeObject(facilityCharges), jsonFacilityChargesDefinition),
totalAmount = totalAmount,
otherPaymentSources = JsonConvert.DeserializeAnonymousType(JsonConvert.SerializeObject(facilityOtherPayments), jsonFacilityOtherPaymentsDefinition)
},
signature = new
{
facility = new
{
signedBy = invoice.SignatureName,
signedOn = DateTime.ParseExact(invoice.SignatureDate, "M/d/yyyy h:mm:ss tt", CultureInfo.InvariantCulture).ToString("MM/dd/yyyy h:mm tt"),
title = invoice.SignatureFacilityTitleName,
ipAddress = invoice.IpAddress
},
client = new
{
... // removed intencionally
},
...
}
};
string invoiceHtml = template(data);
invoiceHtml has the final HTML with data, which is used to generate the PDF by using iText7. Here part of the code:
ConverterProperties converterProperties = new ConverterProperties();
// Create a Temp PDF file temporary, remove this when the whole invoice is created to manaqge it as stream byte[]
string file = System.IO.Path.Combine(basePath, $@"Documents\Temp_Invoice_{invoice.FacilityInvoiceId}.pdf");
PdfWriter writer = new PdfWriter(file);
PdfDocument pdf = new PdfDocument(writer);
pdf.SetDefaultPageSize(PageSize.LEGAL);
var document = HtmlConverter.ConvertToDocument(invoiceHtml, pdf, converterProperties);
document.Close();
Here is is the PDF document in which you can see the section of signers prints across a page break. In this case if data printed in the above section (charges) is too much to produce signers section prints in this way, I need to move signers block to a new page. Even, if the charges section (Invoice Totals) is too much, also to fit in first page, I need to move it to the next page. Here is the screenshot:
I tried by recovering IElements by using ConverToElements, but it gives me the whole element objects with no page these belong to.
Thanks for any help.
The CSS property page-break-inside
is processed by iText 7's HTML to PDF conversion to avoid page breaks within an element. On the table that you don't want to split over two pages:
<table style="page-break-inside: avoid;">
<!-- table content -->
</table>
Of course, if the table grows to a height that is larger than a page, it's inevitable to split it.
Alternatively, when converting to Elements first with ConvertToElements
, you can use SetKeepTogether(true)
on the resulting Table
instance.