Invalid Date

You spent hours perfecting that HTML table. Clean columns, zebra striping, a polished header row. Then you generate the PDF and find your header missing on page two, rows sliced in half at the page boundary, and your totals row stranded at the bottom of the wrong page.

Tables are the most common source of layout problems in HTML to PDF conversion. This guide covers every technique you need — from basic break-inside control to complex colspan/rowspan handling — with a complete invoice table example at the end.

Why Tables Break in PDF

Browsers display content in a continuous scroll with no concept of pages. PDF engines slice content into fixed-height pages. When a table row lands exactly on a page boundary, the engine has to decide: split the row, or push the whole row to the next page?

Without CSS instructions, most engines split the row — which is almost never what you want.

Common symptoms:

Symptom Cause
Rows sliced in half across pages Missing break-inside on tr
No header row on page 2+ thead not set to repeat
Layout breaks with colspan/rowspan Cell merging conflicts with page breaks
Total row isolated at page bottom Footer row not grouped with nearby rows

Basic Page Break Control

break-inside: avoid on Rows

The single most impactful fix: tell the PDF engine never to split a table row.

/* Never split a row across pages */
tr {
  break-inside: avoid;
  page-break-inside: avoid; /* Fallback for older engines */
}

This alone eliminates the "row sliced in half" problem. Start here before trying anything else.

Keep Small Tables on One Page

If a table is short enough to fit on one page, prevent it from splitting at all:

/* Keep the entire table on one page (only works if it fits) */
table {
  break-inside: avoid;
  page-break-inside: avoid;
}

For longer tables, use the grouped tbody strategy described later.

Control Cell Text Wrapping

Long cell content increases row height, making it more likely to hit a page boundary:

td, th {
  /* Wrap long words instead of overflowing */
  overflow-wrap: break-word;
  word-break: break-word;

  /* Prevent URL or code strings from stretching the column */
  max-width: 200px;
  white-space: normal;
}

Repeating thead on Every Page

For tables that span multiple pages, you want the header row to appear at the top of every page — not just the first.

/* Repeat the header row at the top of each page */
thead {
  display: table-header-group;
}

/* Pin the footer (totals row) to the bottom of the last page */
tfoot {
  display: table-footer-group;
}

HTML Structure Requirements

The repeat behavior only works when your HTML structure is correct. Make sure you have explicit <thead>, <tbody>, and optionally <tfoot> elements:

<table>
  <!-- Always use an explicit thead -->
  <thead>
    <tr>
      <th>Item</th>
      <th>Qty</th>
      <th>Unit Price</th>
      <th>Amount</th>
    </tr>
  </thead>

  <!-- All data rows go in tbody -->
  <tbody>
    <tr>
      <td>Web Development</td>
      <td>1</td>
      <td>$8,000</td>
      <td>$8,000</td>
    </tr>
    <!-- more rows... -->
  </tbody>

  <!-- tfoot pins the totals row to the last page -->
  <tfoot>
    <tr>
      <td colspan="3">Total</td>
      <td>$8,800</td>
    </tr>
  </tfoot>
</table>

Note on tfoot: Using tfoot always places the row at the bottom of the last page, which can create a large gap if your last page is mostly empty. If you want the totals row immediately after the last data row, keep it in tbody and use the grouped approach described next.

Styling the Repeated Header

Since the header repeats on every page, make it visually distinct so readers always know where they are:

thead {
  display: table-header-group;
}

thead tr {
  background-color: #1e3a5f;
  color: #ffffff;

  /* Required to output background colors in PDF */
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

thead th {
  padding: 10px 14px;
  font-weight: 700;
  font-size: 10pt;
  text-align: left;
  border-bottom: 2px solid #0f2443;
}

/* Add a subtle top border on repeated headers */
@media print {
  thead {
    border-top: 1px solid #e2e8f0;
  }
}

Page Break Strategy for Long Tables

Multiple tbody Groups

The most practical pattern for long tables: split <tbody> into logical groups and apply break-inside: avoid to each group. This is ideal for invoices with monthly sections, reports with categories, or any table with natural groupings.

<table>
  <thead>
    <tr>
      <th>Month</th>
      <th>Item</th>
      <th>Amount</th>
    </tr>
  </thead>

  <!-- January group — moves to the next page as a unit -->
  <tbody style="break-inside: avoid; page-break-inside: avoid;">
    <tr>
      <td rowspan="3">January</td>
      <td>Development</td>
      <td>$5,000</td>
    </tr>
    <tr>
      <td>Server costs</td>
      <td>$500</td>
    </tr>
    <tr class="subtotal">
      <td>January subtotal</td>
      <td>$5,500</td>
    </tr>
  </tbody>

  <!-- February group -->
  <tbody style="break-inside: avoid; page-break-inside: avoid;">
    <tr>
      <td rowspan="2">February</td>
      <td>Development</td>
      <td>$4,800</td>
    </tr>
    <tr>
      <td>Server costs</td>
      <td>$500</td>
    </tr>
  </tbody>
</table>

Or apply it in CSS:

/* Apply to all tbody groups at once */
tbody {
  break-inside: avoid;
  page-break-inside: avoid;
}

/* Subtotal row styling */
tr.subtotal td {
  font-weight: 700;
  background-color: #f0f4ff;
  border-top: 2px solid #c7d7f0;
  border-bottom: 2px solid #c7d7f0;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

Forcing a Page Break Inside a Table

Sometimes you want to force a page break at a specific point in the table. Use a zero-height row:

<!-- Force a page break before a specific tbody -->
<tbody>
  <tr class="table-page-break">
    <td colspan="4" style="
      break-before: page;
      page-break-before: always;
      padding: 0;
      height: 0;
      border: none;
      font-size: 0;
    "></td>
  </tr>
  <tr>
    <td>First row of new page</td>
    <!-- ... -->
  </tr>
</tbody>

Or with a CSS class:

.table-page-break {
  break-before: page;
  page-break-before: always;
}

.table-page-break td {
  padding: 0;
  height: 0;
  border: none;
  font-size: 0;
}

Handling Complex Tables (colspan / rowspan)

Tables with cell merging are the trickiest to handle across page breaks.

The rowspan Problem

When a rowspan="4" cell lands near a page boundary, the merged cell gets cut off and the layout breaks.

Problematic pattern:

<!-- If this row falls near the page boundary, layout breaks -->
<tr>
  <td rowspan="4">Category A</td>  <!-- ← cut off if at page bottom -->
  <td>Item 1</td>
  <td>$1,000</td>
</tr>
<tr>
  <td>Item 2</td>
  <td>$2,000</td>
</tr>

Solution: wrap the rowspan group in a tbody

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th>Item</th>
      <th>Amount</th>
    </tr>
  </thead>

  <!-- Rowspan group wrapped in tbody — prevents splitting -->
  <tbody style="break-inside: avoid;">
    <tr>
      <td rowspan="4">Category A</td>
      <td>Item 1</td>
      <td>$1,000</td>
    </tr>
    <tr><td>Item 2</td><td>$2,000</td></tr>
    <tr><td>Item 3</td><td>$1,500</td></tr>
    <tr class="subtotal">
      <td>Subtotal</td><td>$4,500</td>
    </tr>
  </tbody>

  <tbody style="break-inside: avoid;">
    <tr>
      <td rowspan="3">Category B</td>
      <td>Item A</td>
      <td>$3,000</td>
    </tr>
    <tr><td>Item B</td><td>$2,500</td></tr>
    <tr class="subtotal">
      <td>Subtotal</td><td>$5,500</td>
    </tr>
  </tbody>
</table>

Multi-level Headers with colspan

Multi-row headers with colspan also repeat correctly when placed inside <thead>:

<table>
  <thead>
    <!-- Both header rows are included in thead — both repeat -->
    <tr>
      <th rowspan="2">Item</th>
      <th colspan="2">Q1</th>
      <th colspan="2">Q2</th>
    </tr>
    <tr>
      <th>Budget</th>
      <th>Actual</th>
      <th>Budget</th>
      <th>Actual</th>
    </tr>
  </thead>
  <tbody>
    <!-- ... -->
  </tbody>
</table>
/* Styling for two-level header */
thead tr:first-child th {
  background-color: #1e3a5f;
  color: #ffffff;
  text-align: center;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

thead tr:last-child th {
  background-color: #2d5585;
  color: #ffffff;
  font-size: 9pt;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

Responsive Tables in PDF

Responsive tables designed for mobile screens often look wrong in PDF — either triggering mobile layout or overflowing the page.

Scale Down Wide Tables

/* On screen: horizontal scroll */
.table-wrapper {
  overflow-x: auto;
}

/* In PDF: scale down to fit the page width */
@media print {
  .table-wrapper {
    overflow: visible;
  }

  .table-responsive {
    width: 100%;
    font-size: 8pt;
    table-layout: fixed;
  }

  .table-responsive td,
  .table-responsive th {
    padding: 5px 6px;
    word-break: break-word;
  }
}

Restore Card Layout to Table for PDF

If you use a card-style responsive table (where <table> is set to display: block on small screens), reset it back to normal table display in PDF:

/* Mobile: card layout */
@media screen and (max-width: 768px) {
  table, thead, tbody, th, td, tr {
    display: block;
  }
  thead tr {
    display: none;
  }
  td::before {
    content: attr(data-label);
    font-weight: 700;
    display: inline-block;
    width: 40%;
  }
}

/* PDF: restore normal table display */
@media print {
  table  { display: table; }
  thead  { display: table-header-group; }
  tbody  { display: table-row-group; }
  tr     { display: table-row; }
  th, td { display: table-cell; }
  thead tr   { display: table-row; }
  td::before { display: none; }
}

Fixed Column Widths for Consistency

PDF engines and browsers calculate column widths differently. Use table-layout: fixed with explicit widths for consistent output:

table {
  table-layout: fixed;
  width: 100%;
}

/* Define column widths explicitly */
.col-item   { width: 40%; }
.col-qty    { width: 10%; }
.col-price  { width: 20%; }
.col-total  { width: 20%; }
.col-note   { width: 10%; }
<table>
  <colgroup>
    <col class="col-item">
    <col class="col-qty">
    <col class="col-price">
    <col class="col-total">
    <col class="col-note">
  </colgroup>
  <thead>
    <tr>
      <th>Item</th>
      <th>Qty</th>
      <th>Unit Price</th>
      <th>Total</th>
      <th>Tax</th>
    </tr>
  </thead>
  <!-- ... -->
</table>

Complete Table CSS Reference

A production-ready CSS for PDF tables:

/* === Base table styles === */
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 10pt;
  line-height: 1.5;
  table-layout: fixed;
  page-break-inside: auto;
}

/* === Header (repeats on every page) === */
thead {
  display: table-header-group;
}

thead tr {
  background-color: #1e40af;
  color: #ffffff;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

thead th {
  padding: 10px 12px;
  font-weight: 700;
  text-align: left;
  border: 1px solid #1e3a8a;
  font-size: 9.5pt;
}

/* === Footer (pinned to last page) === */
tfoot {
  display: table-footer-group;
}

tfoot tr {
  background-color: #f0f4ff;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

tfoot td {
  padding: 10px 12px;
  font-weight: 700;
  border: 1px solid #c7d2fe;
  border-top: 2px solid #4f46e5;
}

/* === Data rows === */
tr {
  break-inside: avoid;
  page-break-inside: avoid;
}

td {
  padding: 8px 12px;
  border: 1px solid #e2e8f0;
  vertical-align: top;
  overflow-wrap: break-word;
  word-break: break-word;
}

/* Zebra striping */
tbody tr:nth-child(even) td {
  background-color: #f8fafc;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

/* Subtotal row */
tr.subtotal td {
  font-weight: 700;
  background-color: #eff6ff;
  border-top: 2px solid #bfdbfe;
  border-bottom: 2px solid #bfdbfe;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

/* Right-aligned amounts */
td.amount, th.amount {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

Complete Example: Invoice with Line Items

Here's a full invoice table implementation with repeating headers, grouped tbody sections, and a total block that stays together at the end.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    @page {
      size: A4;
      margin: 20mm 15mm;
    }

    body {
      font-family: -apple-system, 'Segoe UI', sans-serif;
      font-size: 10pt;
      color: #1f2937;
    }

    /* ===== Invoice header ===== */
    .invoice-header {
      display: flex;
      justify-content: space-between;
      margin-bottom: 24pt;
      break-inside: avoid;
    }

    .invoice-title {
      font-size: 24pt;
      font-weight: 700;
      color: #1e40af;
    }

    /* ===== Line items table ===== */
    .line-items-table {
      width: 100%;
      border-collapse: collapse;
      font-size: 9.5pt;
      table-layout: fixed;
    }

    /* Column widths */
    .col-no     { width: 6%; }
    .col-desc   { width: 40%; }
    .col-qty    { width: 10%; }
    .col-unit   { width: 18%; }
    .col-amount { width: 18%; }
    .col-tax    { width: 8%; }

    /* Header — repeats on every page */
    .line-items-table thead {
      display: table-header-group;
    }

    .line-items-table thead tr {
      background-color: #1e3a5f;
      color: #ffffff;
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }

    .line-items-table thead th {
      padding: 9px 10px;
      font-weight: 700;
      border: 1px solid #0f2443;
      text-align: left;
    }

    /* Rows — never split across pages */
    .line-items-table tbody tr {
      break-inside: avoid;
      page-break-inside: avoid;
    }

    .line-items-table tbody td {
      padding: 7px 10px;
      border: 1px solid #d1d5db;
      vertical-align: top;
      overflow-wrap: break-word;
    }

    .line-items-table tbody tr:nth-child(even) td {
      background-color: #f9fafb;
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }

    .align-right { text-align: right; }
    .font-mono   { font-variant-numeric: tabular-nums; }

    /* Category header rows */
    .line-items-table tr.category-header td {
      background-color: #dbeafe;
      font-weight: 700;
      font-size: 9pt;
      color: #1e3a8a;
      padding: 5px 10px;
      border-top: 2px solid #93c5fd;
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }

    /* Subtotal rows */
    .line-items-table tr.subtotal td {
      font-weight: 700;
      background-color: #eff6ff;
      border-top: 2px solid #bfdbfe;
      border-bottom: 2px solid #bfdbfe;
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }

    /* ===== Totals block — never splits ===== */
    .total-section {
      break-inside: avoid;
      page-break-inside: avoid;
      border: 1px solid #d1d5db;
      border-top: none;
    }

    .total-section table {
      width: 100%;
      border-collapse: collapse;
    }

    .total-section td {
      padding: 7px 10px;
      border-bottom: 1px solid #e5e7eb;
    }

    .total-section .total-row {
      background-color: #1e3a5f;
      color: #ffffff;
      font-size: 12pt;
      font-weight: 700;
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }

    .total-section .total-row td {
      border-bottom: none;
    }
  </style>
</head>
<body>

  <div class="invoice-header">
    <div>
      <div class="invoice-title">Invoice</div>
      <p>Invoice #: INV-2026-0042</p>
      <p>Date: April 5, 2026</p>
      <p>Due: April 30, 2026</p>
    </div>
    <div style="text-align: right;">
      <p style="font-weight: 700; font-size: 12pt;">Sample Corp.</p>
      <p>123 Business St, Suite 100</p>
    </div>
  </div>

  <!-- Line items table — thead repeats automatically on each page -->
  <table class="line-items-table">
    <colgroup>
      <col class="col-no">
      <col class="col-desc">
      <col class="col-qty">
      <col class="col-unit">
      <col class="col-amount">
      <col class="col-tax">
    </colgroup>

    <thead>
      <tr>
        <th class="align-right">#</th>
        <th>Description</th>
        <th class="align-right">Qty</th>
        <th class="align-right">Unit Price</th>
        <th class="align-right">Amount</th>
        <th>Tax</th>
      </tr>
    </thead>

    <!-- Development group — moves to next page as a unit -->
    <tbody style="break-inside: avoid;">
      <tr class="category-header">
        <td colspan="6">Development Services</td>
      </tr>
      <tr>
        <td class="align-right">1</td>
        <td>Web Application Development<br>
          <span style="font-size:8.5pt; color:#6b7280;">Frontend implementation (React)</span>
        </td>
        <td class="align-right font-mono">1</td>
        <td class="align-right font-mono">$8,000</td>
        <td class="align-right font-mono">$8,000</td>
        <td>Taxable</td>
      </tr>
      <tr>
        <td class="align-right">2</td>
        <td>Backend API Development<br>
          <span style="font-size:8.5pt; color:#6b7280;">REST API design and implementation (Node.js)</span>
        </td>
        <td class="align-right font-mono">1</td>
        <td class="align-right font-mono">$6,000</td>
        <td class="align-right font-mono">$6,000</td>
        <td>Taxable</td>
      </tr>
      <tr>
        <td class="align-right">3</td>
        <td>Database Design and Setup</td>
        <td class="align-right font-mono">1</td>
        <td class="align-right font-mono">$2,000</td>
        <td class="align-right font-mono">$2,000</td>
        <td>Taxable</td>
      </tr>
      <tr class="subtotal">
        <td colspan="4" class="align-right">Development Subtotal</td>
        <td class="align-right font-mono">$16,000</td>
        <td></td>
      </tr>
    </tbody>

    <!-- Infrastructure group -->
    <tbody style="break-inside: avoid;">
      <tr class="category-header">
        <td colspan="6">Infrastructure &amp; Misc</td>
      </tr>
      <tr>
        <td class="align-right">4</td>
        <td>Cloud Server Setup<br>
          <span style="font-size:8.5pt; color:#6b7280;">AWS EC2/RDS configuration, VPC setup</span>
        </td>
        <td class="align-right font-mono">1</td>
        <td class="align-right font-mono">$1,500</td>
        <td class="align-right font-mono">$1,500</td>
        <td>Taxable</td>
      </tr>
      <tr>
        <td class="align-right">5</td>
        <td>Domain and SSL Certificate</td>
        <td class="align-right font-mono">1</td>
        <td class="align-right font-mono">$300</td>
        <td class="align-right font-mono">$300</td>
        <td>Taxable</td>
      </tr>
      <tr class="subtotal">
        <td colspan="4" class="align-right">Infrastructure Subtotal</td>
        <td class="align-right font-mono">$1,800</td>
        <td></td>
      </tr>
    </tbody>
  </table>

  <!-- Totals block — kept together with the table, never splits -->
  <div class="total-section">
    <table>
      <tr>
        <td style="width:70%; text-align:right; color:#6b7280;">Subtotal</td>
        <td style="width:20%; text-align:right;" class="font-mono">$17,800</td>
        <td style="width:10%;"></td>
      </tr>
      <tr>
        <td style="text-align:right; color:#6b7280;">Tax (10%)</td>
        <td style="text-align:right;" class="font-mono">$1,780</td>
        <td></td>
      </tr>
      <tr class="total-row">
        <td style="text-align:right;">Total Due</td>
        <td style="text-align:right;" class="font-mono">$19,580</td>
        <td></td>
      </tr>
    </table>
  </div>

</body>
</html>

Using FUNBREW PDF API

Convert the HTML above to a PDF with the FUNBREW PDF API using the quality engine (Chromium-based, with full CSS support):

JavaScript (Node.js)

const fs = require('fs');

const html = fs.readFileSync('./invoice-template.html', 'utf8');

const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk-your-api-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html,
    options: {
      engine: 'quality',       // Chromium engine — full CSS page break support
      format: 'A4',
      printBackground: true,   // Required to render background colors
      margin: {
        top: '20mm',
        bottom: '20mm',
        left: '15mm',
        right: '15mm',
      },
    },
  }),
});

const result = await response.json();
console.log('PDF URL:', result.data.download_url);

Python

import requests

with open('./invoice-template.html', 'r') as f:
    html = f.read()

response = requests.post(
    'https://pdf.funbrew.cloud/api/pdf/generate',
    headers={'Authorization': 'Bearer sk-your-api-key'},
    json={
        'html': html,
        'options': {
            'engine': 'quality',
            'format': 'A4',
            'printBackground': True,
            'margin': {
                'top': '20mm',
                'bottom': '20mm',
                'left': '15mm',
                'right': '15mm',
            },
        },
    },
)

print('PDF URL:', response.json()['data']['download_url'])

PHP

$html = file_get_contents(resource_path('views/pdf/invoice.html'));

$response = Http::withToken('sk-your-api-key')
    ->post('https://pdf.funbrew.cloud/api/pdf/generate', [
        'html' => $html,
        'options' => [
            'engine' => 'quality',
            'format' => 'A4',
            'printBackground' => true,
            'margin' => [
                'top' => '20mm',
                'bottom' => '20mm',
                'left' => '15mm',
                'right' => '15mm',
            ],
        ],
    ]);

$downloadUrl = $response->json('data.download_url');

Using the Template Engine

For dynamic tables with many variables, the FUNBREW PDF template engine handles variable substitution automatically:

const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate-from-template', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk-your-api-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    template: 'invoice-with-table',  // Template name from dashboard
    variables: {
      invoice_number: 'INV-2026-0042',
      issue_date: 'April 5, 2026',
      customer_name: 'Acme Corp.',
      items: [
        { no: 1, desc: 'Web Development', qty: 1, unit: 8000, amount: 8000 },
        { no: 2, desc: 'API Development', qty: 1, unit: 6000, amount: 6000 },
      ],
      subtotal: '17,800',
      tax: '1,780',
      total: '19,580',
    },
    options: { engine: 'quality', format: 'A4' },
  }),
});

Troubleshooting Reference

Symptom Cause Fix
Rows split in half across pages Missing break-inside Add tr { break-inside: avoid; }
No header on page 2+ thead not set to repeat Add thead { display: table-header-group; }
Totals row appears in the wrong spot tfoot pinned to last page Move totals to tbody and group with break-inside: avoid
rowspan layout breaks Cell merge conflicts with page break Wrap rowspan group in tbody with break-inside: avoid
Background colors missing Engine default setting Set both print-color-adjust: exact and printBackground: true
Table overflows the page Fixed-width columns Use table-layout: fixed with overflow-wrap: break-word
Column widths differ between browser and PDF Engine width calculation differences Specify widths with <colgroup>
Numbers don't align Proportional font Apply font-variant-numeric: tabular-nums

Debugging Tips

  1. Chrome print preview: In DevTools, press Ctrl+Shift+P → "Emulate CSS media type" → "print" to preview @media print styles
  2. Playground for instant testing: Paste your HTML and see the PDF output immediately
  3. Use the quality engine: FUNBREW PDF's quality engine (Chromium-based) has the most complete CSS page break support. If you're seeing issues with fast, switch to quality first
  4. Start minimal: Test with a 5-row table before adding complexity — confirm the basic behavior is correct, then add colspan/rowspan/categories

Summary

Key takeaways for PDF table layouts:

  • Row splitting: tr { break-inside: avoid; } — the single most important fix
  • Repeating headers: thead { display: table-header-group; } — automatic on every page
  • Group management: Multiple tbody elements + break-inside: avoid — keeps categories, rowspan groups, and subtotal rows together
  • Complex cells: Wrap colspan/rowspan groups in tbody to prevent layout breaks
  • Responsive tables: Use @media print to restore normal table display and table-layout: fixed for stable column widths
  • Background colors: print-color-adjust: exact in CSS + printBackground: true in API options — always set both
  • Debugging: Chrome print preview + Playground for quick iteration

Test your table HTML in the Playground and iterate until the layout is exactly right. For full invoice automation including template management and batch generation, see the invoice PDF automation guide.

Related Links

Powered by FUNBREW PDF