Developers using PDFTron (now Apryse) HTML2PDF encounter CSS rendering failures where valid HTML and CSS produce incorrect output. Common problems include tables rendering incorrectly, display: table-header-group not working, cell padding causing visual artifacts, and table row overhang. Despite using Chromium headless internally, the module does not produce output matching browser rendering. This article documents the CSS rendering limitations and examines alternatives with full browser compatibility.
The Problem
The PDFTron HTML2PDF module fails to render standard HTML table structures and CSS properties correctly. Developers report that even basic HTML and CSS combinations produce unexpected results:
- Tables with
<thead>and<tfoot>tags do not render properly - The CSS property
display: table-header-groupdoes not work - The CSS property
display: table-footer-groupdoes not work - Cell padding values cause table row overhang
- Headers and footers do not repeat across pages as expected
The issue is particularly frustrating because PDFTron documentation states the module uses Chromium headless for rendering. Developers expect output matching Chrome's print preview, but the actual results differ significantly.
Error Messages and Symptoms
There are typically no error messages - the conversion completes but produces incorrect output:
<!-- Expected to repeat headers across pages - does not work -->
<table>
<thead style="display: table-header-group;">
<tr>
<th>Column 1</th>
<th>Column 2</th>
</tr>
</thead>
<tbody>
<!-- Multiple rows spanning pages -->
</tbody>
<tfoot style="display: table-footer-group;">
<tr>
<td>Footer 1</td>
<td>Footer 2</td>
</tr>
</tfoot>
</table>
Observed symptoms:
- Table headers appear only on first page
- Table footers appear only on last page
- Cell padding of 4px causes visual overhang on table rows
- Output differs from Chrome print preview despite using "Chromium headless"
Who Is Affected
This issue impacts developers creating multi-page documents with tables:
Document Types: Reports, invoices, data exports, financial statements, any document with tabular data spanning multiple pages.
Use Cases:
- Invoice generation with line items
- Report generation with data tables
- Document templates with repeating headers
- Any table that spans page breaks
Frameworks: The issue affects all PDFTron SDK implementations including JavaScript, .NET, Java, and other language bindings.
Evidence from the Developer Community
Timeline
| Date | Event | Source |
|---|---|---|
| 2023-10-16 | Issue reported with table and CSS rendering | Apryse Community |
| 2023-10-31 | Last community activity | Apryse Community |
Community Reports
"I am using as basic of HTML and CSS as you can do and still things are not working"
— Developer, Apryse Community, October 2023"Tables I noticed that it does not like when I use the or HTML tags, and if i add with cell padding of 4px i get weird table row overhang"
— Developer, Apryse Community, October 2023"The following CSS properties don't seem to work to achieve the repeating header and footer rows across pages: display: table-header-group; display: table-footer-group;"
— Developer, Apryse Community, October 2023
The thread accumulated over 3,600 views, indicating widespread interest in this issue.
Root Cause Analysis
The official Apryse response acknowledges the limitation:
"The module is using Chromium headless, so generally speaking, you can preview your HTML in Chrome print dialog, and Apryse HTML2PDF module output."
However, this statement does not match developer experience. The disconnect arises from several factors:
Header/Footer Template Limitations: PDFTron's header and footer system uses a separate HTML template mechanism rather than native CSS table rendering. This means standard CSS properties like
display: table-header-groupmay not behave as expected.Chromium Version: The embedded Chromium version may lag behind current Chrome releases, missing CSS rendering improvements.
Print Context Differences: Chromium headless in print mode behaves differently than Chrome's interactive print preview. Certain CSS features that work in browser print preview may not work in headless mode.
Undocumented Behavior: As noted in Apryse's documentation, header/footer CSS support is "not 100% documented by Chromium," making behavior unpredictable.
Attempted Workarounds
Workaround 1: Use JavaScript to Manually Repeat Headers
Approach: Generate HTML with headers pre-duplicated at calculated page break positions.
// Calculate page height and manually insert headers
function generateHtmlWithRepeatingHeaders(data, pageHeight) {
let html = '';
let currentHeight = 0;
const headerHtml = '<tr class="header"><th>Col 1</th><th>Col 2</th></tr>';
data.forEach((row, index) => {
if (currentHeight > pageHeight) {
html += headerHtml; // Manually add header
currentHeight = 0;
}
html += `<tr><td>${row.col1}</td><td>${row.col2}</td></tr>`;
currentHeight += 30; // Estimated row height
});
return html;
}
Limitations:
- Requires calculating page dimensions manually
- Row heights vary with content, making calculations unreliable
- Fragile solution that breaks with content changes
- Adds significant complexity
Workaround 2: Avoid CSS Table Display Properties
Approach: Do not use display: table-header-group or display: table-footer-group.
Limitations:
- Loses automatic header/footer repetition across pages
- Headers appear only on first page
- Not acceptable for professional documents
Workaround 3: Use PDFTron's Header/Footer API Instead of CSS
Approach: Use the API's dedicated header and footer template parameters.
const html2pdf = await PDFNet.HTML2PDF.create();
html2pdf.setHeader('<header html>');
html2pdf.setFooter('<footer html>');
Limitations:
- Separate from table structure - headers are page headers, not table headers
- Limited HTML/CSS support within templates
- Does not solve repeating table headers problem
- Images may not appear in header/footer templates (documented limitation)
A Different Approach: IronPDF
IronPDF uses a fully embedded Chrome browser engine that produces output matching Chrome's print preview. Table CSS properties work as expected, including repeating headers and footers across pages.
Why IronPDF CSS Tables Work Correctly
IronPDF's rendering engine:
- Uses current Chromium with full CSS support
- Processes
display: table-header-groupcorrectly - Repeats table headers on each page automatically
- Handles cell padding without visual artifacts
- Matches Chrome print preview output
Code Example
using IronPdf;
public class ReportGenerator
{
public byte[] GenerateMultiPageReport(IEnumerable<SalesData> salesData)
{
var renderer = new ChromePdfRenderer();
// Configure for print-quality output
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
string html = $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{
font-family: Arial, sans-serif;
margin: 20px;
}}
table {{
width: 100%;
border-collapse: collapse;
}}
/* These CSS properties work correctly in IronPDF */
thead {{
display: table-header-group;
}}
tfoot {{
display: table-footer-group;
}}
th, td {{
padding: 8px 12px;
border: 1px solid #ddd;
text-align: left;
}}
th {{
background-color: #2c3e50;
color: white;
}}
tfoot td {{
background-color: #f5f5f5;
font-weight: bold;
}}
/* Page break handling */
tr {{
page-break-inside: avoid;
}}
</style>
</head>
<body>
<h1>Sales Report</h1>
<table>
<thead>
<tr>
<th>Date</th>
<th>Product</th>
<th>Region</th>
<th>Quantity</th>
<th>Revenue</th>
</tr>
</thead>
<tbody>
{GenerateTableRows(salesData)}
</tbody>
<tfoot>
<tr>
<td colspan='3'>Total</td>
<td>{salesData.Sum(s => s.Quantity)}</td>
<td>${salesData.Sum(s => s.Revenue):N2}</td>
</tr>
</tfoot>
</table>
</body>
</html>";
using var pdf = renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}
private string GenerateTableRows(IEnumerable<SalesData> data)
{
var sb = new StringBuilder();
foreach (var item in data)
{
sb.AppendLine($@"
<tr>
<td>{item.Date:yyyy-MM-dd}</td>
<td>{item.Product}</td>
<td>{item.Region}</td>
<td>{item.Quantity}</td>
<td>${item.Revenue:N2}</td>
</tr>");
}
return sb.ToString();
}
}
public class SalesData
{
public DateTime Date { get; set; }
public string Product { get; set; }
public string Region { get; set; }
public int Quantity { get; set; }
public decimal Revenue { get; set; }
}
Key points about this code:
-
display: table-header-groupworks correctly - headers repeat on every page -
display: table-footer-groupplaces footer at the bottom of the last page - Cell padding renders without visual artifacts
- Output matches Chrome print preview exactly
- No manual page break calculations needed
Handling Complex Table Layouts
IronPDF also supports more complex CSS table scenarios:
using IronPdf;
public class InvoiceWithComplexTables
{
public byte[] GenerateInvoice()
{
var renderer = new ChromePdfRenderer();
string html = @"
<!DOCTYPE html>
<html>
<head>
<style>
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
/* Nested tables work correctly */
table table {
margin: 0;
}
/* Cell padding with specific values - no overhang */
td, th {
padding: 4px 8px;
border: 1px solid #ccc;
}
/* Colspan and rowspan render correctly */
.merged-header {
text-align: center;
background-color: #34495e;
color: white;
}
/* Repeating headers across pages */
thead { display: table-header-group; }
tfoot { display: table-footer-group; }
/* Prevent row splitting */
tr { page-break-inside: avoid; }
</style>
</head>
<body>
<table>
<thead>
<tr>
<th colspan='4' class='merged-header'>Quarterly Sales Summary</th>
</tr>
<tr>
<th>Product</th>
<th>Q1</th>
<th>Q2</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Product A</td>
<td>$10,000</td>
<td>$12,000</td>
<td>$22,000</td>
</tr>
<!-- Additional rows -->
</tbody>
<tfoot>
<tr>
<td>Totals</td>
<td>$50,000</td>
<td>$60,000</td>
<td>$110,000</td>
</tr>
</tfoot>
</table>
</body>
</html>";
using var pdf = renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}
}
API Reference
Migration Considerations
Licensing
- PDFTron/Apryse is commercial software with various licensing tiers
- IronPDF is commercial with perpetual licensing
- IronPDF Licensing Details
API Differences
PDFTron HTML2PDF:
const html2pdf = await PDFNet.HTML2PDF.create();
const doc = await PDFNet.PDFDoc.create();
html2pdf.insertFromHtmlString(htmlContent);
await html2pdf.convert(doc);
IronPDF:
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
What You Gain
- Full CSS table support including
display: table-header-group - Repeating table headers and footers work automatically
- Cell padding renders without visual artifacts
- Output matches Chrome browser exactly
- Current Chromium engine with latest CSS support
What to Consider
- Different API structure
- Commercial licensing required for both libraries
- Different language ecosystem (though IronPDF supports JavaScript via Node.js)
Conclusion
PDFTron HTML2PDF's CSS rendering limitations prevent standard table features from working correctly. Despite using Chromium headless, developers cannot rely on display: table-header-group, display: table-footer-group, or even basic cell padding to render as expected. For documents requiring tables with repeating headers across pages, accurate cell spacing, and browser-matching output, libraries with full Chrome rendering compatibility provide the CSS support that custom implementations lack.
Jacob Mellor has 25+ years building developer tools and originally created IronPDF.
References
- Apryse Community Thread #8549{:rel="nofollow"} - HTML2PDF Module not rendering valid html or css correctly
- Apryse HTML2PDF Header/Footer Documentation{:rel="nofollow"} - Official documentation on header/footer limitations
For the latest IronPDF documentation and tutorials, visit ironpdf.com.
Top comments (0)