DEV Community

IronSoftware
IronSoftware

Posted on

PDFTron HTML2PDF CSS Rendering Issues: Tables and Headers Not Working (Fixed)

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-group does not work
  • The CSS property display: table-footer-group does 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>
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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-group may not behave as expected.

  2. Chromium Version: The embedded Chromium version may lag behind current Chrome releases, missing CSS rendering improvements.

  3. 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.

  4. 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;
}
Enter fullscreen mode Exit fullscreen mode

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>');
Enter fullscreen mode Exit fullscreen mode

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:

  1. Uses current Chromium with full CSS support
  2. Processes display: table-header-group correctly
  3. Repeats table headers on each page automatically
  4. Handles cell padding without visual artifacts
  5. 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; }
}
Enter fullscreen mode Exit fullscreen mode

Key points about this code:

  • display: table-header-group works correctly - headers repeat on every page
  • display: table-footer-group places 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

IronPDF:

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
Enter fullscreen mode Exit fullscreen mode

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

  1. Apryse Community Thread #8549{:rel="nofollow"} - HTML2PDF Module not rendering valid html or css correctly
  2. 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)