# Print styling guide

The PDF renderer turns HTML and CSS into paginated output. This guide covers page layout, typography, tables, charts, and print-specific CSS for invoices, reports, contracts, and certificates.

---

## Page layout

The `@page` at-rule controls the document's page structure.

### Page size

```css
/* Standard paper sizes */
@page {
  size: A4;           /* 210mm x 297mm */
}

@page {
  size: Letter;       /* 8.5in x 11in */
}

@page {
  size: Legal;        /* 8.5in x 14in */
}

/* Custom dimensions */
@page {
  size: 8in 10in;     /* Width x Height */
}

/* Landscape orientation */
@page {
  size: A4 landscape;
}
```

### Margins

Page margins control the printable area and space for headers/footers:

```css
@page {
  size: A4;
  margin: 2cm;                    /* All sides equal */
}

@page {
  size: Letter;
  margin: 1in 0.75in;             /* Vertical, Horizontal */
}

@page {
  size: A4;
  margin: 2cm 1.5cm 2cm 1.5cm;    /* Top, Right, Bottom, Left */
}
```

### Named pages

Use named pages for different sections of your document:

```css
@page cover {
  margin: 0;
}

@page chapter {
  margin: 2cm;
  @top-center {
    content: "Chapter Title";
  }
}

.cover-page {
  page: cover;
}

.chapter-content {
  page: chapter;
}
```

### First, left, and right pages

Apply different styles to specific page positions:

```css
/* First page - often no header */
@page :first {
  @top-center {
    content: none;
  }
}

/* Left pages (even) */
@page :left {
  margin-left: 3cm;
  margin-right: 2cm;
}

/* Right pages (odd) */
@page :right {
  margin-left: 2cm;
  margin-right: 3cm;
}
```

---

## Page breaks

### Force page breaks

```css
/* Break before an element */
.chapter-title {
  page-break-before: always;
  break-before: page;
}

/* Break after an element */
.section-end {
  page-break-after: always;
  break-after: page;
}
```

```html
<!-- Force a new page before this element -->
<h1 class="chapter-title">Chapter 2: Advanced Topics</h1>

<!-- Force a new page after this element -->
<div class="section-end">End of Section 1</div>
```

### Prevent page breaks

Keep related content together by preventing breaks inside elements:

```css
/* Prevent breaks inside an element */
.keep-together {
  page-break-inside: avoid;
  break-inside: avoid;
}

/* Common elements to keep together */
figure,
blockquote,
table,
.card,
.info-box {
  page-break-inside: avoid;
  break-inside: avoid;
}
```

### Orphans and widows

Control minimum lines at page boundaries:

```css
p {
  orphans: 3;   /* Minimum lines at bottom of page */
  widows: 3;    /* Minimum lines at top of page */
}
```

### Page-break rules

```css
/* Headings should stay with following content */
h1, h2, h3, h4, h5, h6 {
  page-break-after: avoid;
  break-after: avoid;
}

/* Don't break inside list items */
li {
  page-break-inside: avoid;
  break-inside: avoid;
}

/* Keep images with captions */
figure {
  page-break-inside: avoid;
  break-inside: avoid;
}
```

---

## Headers and footers

Add running headers and footers using CSS margin boxes within the `@page` rule.

### Basic headers and footers

```css
@page {
  size: A4;
  margin: 2.5cm 2cm;

  @top-left {
    content: "Company Name";
    font-size: 10pt;
    color: #666;
  }

  @top-right {
    content: "Confidential";
    font-size: 10pt;
    color: #666;
  }

  @bottom-center {
    content: "Page " counter(page) " of " counter(pages);
    font-size: 9pt;
  }
}
```

### Margin boxes

The following margin box positions are available:

| Position | Location |
|----------|----------|
| `@top-left` | Top margin, left side |
| `@top-center` | Top margin, center |
| `@top-right` | Top margin, right side |
| `@bottom-left` | Bottom margin, left side |
| `@bottom-center` | Bottom margin, center |
| `@bottom-right` | Bottom margin, right side |
| `@left-top` | Left margin, top |
| `@left-middle` | Left margin, middle |
| `@left-bottom` | Left margin, bottom |
| `@right-top` | Right margin, top |
| `@right-middle` | Right margin, middle |
| `@right-bottom` | Right margin, bottom |

### Page counters

Use CSS counters for page numbering:

```css
@page {
  @bottom-right {
    content: "Page " counter(page);
  }
}

/* With total page count */
@page {
  @bottom-center {
    content: counter(page) " / " counter(pages);
  }
}
```

### Dynamic header text

Use CSS string-set to create running headers:

```css
h1 {
  string-set: chapter-title content();
}

@page {
  @top-left {
    content: string(chapter-title);
  }
}
```

### First-page headers

```css
@page :first {
  @top-left {
    content: none;    /* No header on first page */
  }
  @top-right {
    content: none;
  }
}

@page {
  @top-center {
    content: "Document Title";
  }
}
```

### Running elements

The `content` property in margin boxes accepts strings, counters, and `url()`. For logos, styled layouts, or other HTML in a header or footer, combine `position: running()` with `element()`.

Place an HTML element in the document, remove it from normal flow with `position: running(name)`, and reference it from a margin box with `content: element(name)`.

```css
/* Pull element out of the flow */
.running-header {
  position: running(headerContent);
}

/* Place it in the page margin */
@page {
  margin-top: 3cm;

  @top-center {
    content: element(headerContent);
  }
}
```

```html
<!-- This won't appear inline — it goes straight to the margin box -->
<div class="running-header">
  <img src="https://example.com/logo.png" style="height: 20px; vertical-align: middle;" />
  <span style="font-size: 10pt; color: #666; margin-left: 8pt;">Company Name</span>
</div>

<h1>Document Title</h1>
<p>Content starts here...</p>
```

Behavior:

- The running element is removed from document flow and does not appear in page content
- It renders in the margin box on every page
- A later element with the same running name replaces it from that page onward

Use that replacement behavior for chapter-specific headers:

```css
.chapter-header { position: running(chapterHead); }

@page {
  @top-left {
    content: element(chapterHead);
  }
}
```

```html
<section>
  <div class="chapter-header">Chapter 1: Introduction</div>
  <p>Chapter 1 content...</p>
</section>

<section style="break-before: page;">
  <!-- Replaces the running header from this page onward -->
  <div class="chapter-header">Chapter 2: Analysis</div>
  <p>Chapter 2 content...</p>
</section>
```

**`string-set` and `running()` compared:**

| Feature | `string-set` / `string()` | `running()` / `element()` |
|---------|--------------------------|---------------------------|
| Plain text | Yes | Yes |
| Images / logos | No | Yes |
| Styled multi-element HTML | No | Yes |
| Simpler syntax | Yes | No |

Use `string-set` for text such as chapter titles. Use `running()` for logos, icons, or other styled HTML.

---

## Fonts

For predictable output, use a font available to the renderer or embed it in the document.

### Web-safe fonts

These web-safe fonts work without embedding:

```css
body {
  font-family: Arial, Helvetica, sans-serif;
}

.monospace {
  font-family: "Courier New", Courier, monospace;
}

.serif {
  font-family: Georgia, "Times New Roman", serif;
}
```

### Custom fonts

Embed fonts using `@font-face`:

```css
@font-face {
  font-family: 'CustomFont';
  src: url('https://example.com/fonts/custom.woff2') format('woff2'),
       url('https://example.com/fonts/custom.woff') format('woff');
  font-weight: normal;
  font-style: normal;
}

body {
  font-family: 'CustomFont', sans-serif;
}
```

### Google Fonts CDN

Link to Google Fonts directly in your HTML:

```html
<!DOCTYPE html>
<html>
<head>
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
  <style>
    body {
      font-family: 'Roboto', sans-serif;
    }
  </style>
</head>
<body>
  <h1>Your content</h1>
</body>
</html>
```

### Base64-embedded fonts

For offline reliability, embed fonts as base64:

```css
@font-face {
  font-family: 'EmbeddedFont';
  src: url(data:font/woff2;base64,d09GMgABAAAAAA...) format('woff2');
}
```

### Font rules

```css
body {
  /* Use a font stack with fallbacks */
  font-family: 'Primary Font', 'Fallback Font', sans-serif;

  /* Set a readable base size */
  font-size: 11pt;

  /* Ensure good line spacing */
  line-height: 1.5;

  /* Enable font smoothing */
  -webkit-font-smoothing: antialiased;
}

/* Optimize headings */
h1, h2, h3 {
  line-height: 1.2;
  page-break-after: avoid;
}
```

---

## Print media queries

Use `@media print` to apply styles specifically for PDF output:

### Basic print styles

```css
/* Screen-only styles */
@media screen {
  .print-only {
    display: none;
  }

  body {
    background: #f5f5f5;
  }
}

/* Print-only styles */
@media print {
  .no-print,
  .screen-only {
    display: none !important;
  }

  body {
    background: white;
    color: black;
  }

  /* Remove decorative elements */
  nav,
  .sidebar,
  .ads,
  .social-share {
    display: none !important;
  }
}
```

### Printed links

```css
@media print {
  /* Show URL after links */
  a[href^="http"]:after {
    content: " (" attr(href) ")";
    font-size: 0.8em;
    color: #666;
  }

  /* But not for image links or internal links */
  a[href^="#"]:after,
  a:has(img):after {
    content: none;
  }

  /* Remove link styling */
  a {
    color: inherit;
    text-decoration: underline;
  }
}
```

### Print layout adjustments

```css
@media print {
  /* Use full width */
  .container {
    max-width: 100%;
    margin: 0;
    padding: 0;
  }

  /* Flatten multi-column layouts */
  .grid {
    display: block;
  }

  /* Ensure images don't overflow */
  img {
    max-width: 100%;
    height: auto;
  }

  /* Reset background colors */
  * {
    background: transparent !important;
  }
}
```

---

## Tables

Tables need explicit break and header rules when they span pages.

### Basic table styling

```css
table {
  width: 100%;
  border-collapse: collapse;
  page-break-inside: auto;
}

thead {
  display: table-header-group;   /* Repeat header on each page */
}

tfoot {
  display: table-footer-group;   /* Repeat footer on each page */
}

tbody {
  display: table-row-group;
}

tr {
  page-break-inside: avoid;      /* Keep rows together */
  break-inside: avoid;
}
```

### Repeating table headers

Repeat table headers on every page:

```html
<table>
  <thead>
    <tr>
      <th>Column 1</th>
      <th>Column 2</th>
      <th>Column 3</th>
    </tr>
  </thead>
  <tbody>
    <tr><td>Data 1</td><td>Data 2</td><td>Data 3</td></tr>
    <!-- More rows... -->
  </tbody>
</table>
```

```css
thead {
  display: table-header-group;
  background: #f0f0f0;
}

th {
  font-weight: bold;
  text-align: left;
  padding: 8px;
  border-bottom: 2px solid #333;
}
```

### Long tables

```css
/* Allow table to break across pages */
table.long-table {
  page-break-inside: auto;
}

/* But keep individual rows together */
table.long-table tr {
  page-break-inside: avoid;
  break-inside: avoid;
}

/* Minimum height prevents tiny orphan rows */
table.long-table td {
  min-height: 2em;
}
```

### Zebra striping

```css
tbody tr:nth-child(even) {
  background-color: #f9f9f9;
}

@media print {
  tbody tr:nth-child(even) {
    background-color: #f0f0f0 !important;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}
```

### Table borders

```css
table {
  border: 1px solid #ccc;
}

th, td {
  border: 1px solid #ccc;
  padding: 8px 12px;
}

/* Handle borders at page breaks */
@media print {
  table {
    border-collapse: collapse;
  }

  tr {
    border-bottom: 1px solid #ccc;
  }
}
```

---

## Charts

The PDF renderer does not execute JavaScript. Chart.js, D3.js, Plotly.js, Highcharts, and ApexCharts therefore produce no output, and `<canvas>` elements remain blank. Render charts as static SVG or images before adding them to the HTML.

### Inline SVG

SVG is the recommended format because it stays sharp at any zoom level:

```html
<div class="chart">
  <h4>Revenue by Quarter</h4>
  <svg viewBox="0 0 400 220" xmlns="http://www.w3.org/2000/svg">
    <rect x="30" y="120" width="50" height="80" fill="#3498db" rx="2"/>
    <rect x="110" y="60" width="50" height="140" fill="#3498db" rx="2"/>
    <rect x="190" y="30" width="50" height="170" fill="#2ecc71" rx="2"/>
    <rect x="270" y="50" width="50" height="150" fill="#2ecc71" rx="2"/>
    <text x="55" y="215" text-anchor="middle" font-size="11" fill="#333">Q1</text>
    <text x="135" y="215" text-anchor="middle" font-size="11" fill="#333">Q2</text>
    <text x="215" y="215" text-anchor="middle" font-size="11" fill="#333">Q3</text>
    <text x="295" y="215" text-anchor="middle" font-size="11" fill="#333">Q4</text>
    <line x1="20" y1="200" x2="340" y2="200" stroke="#666" stroke-width="1"/>
    <line x1="20" y1="0" x2="20" y2="200" stroke="#666" stroke-width="1"/>
  </svg>
</div>
```

### Embedded images

When SVG isn't available, use base64-encoded or hosted images:

```html
<!-- Base64 encoded -->
<img src="data:image/png;base64,iVBORw0KGgo..."
     alt="Revenue Chart"
     style="width: 100%; max-width: 600px;" />

<!-- Hosted URL -->
<img src="https://example.com/charts/revenue-q4.png"
     alt="Revenue Chart"
     style="width: 100%; max-width: 600px;" />
```

### Chart styles

```css
.chart {
  break-inside: avoid;
  page-break-inside: avoid;
  margin: 1cm 0;
  text-align: center;
}

.chart svg,
.chart img {
  max-width: 100%;
  height: auto;
}

/* SVG text doesn't inherit document fonts */
.chart svg text {
  font-family: Arial, Helvetica, sans-serif;
  fill: #333;
}
```

### Plotly with Python

In a Python script, server, or code interpreter, Plotly can export publication-quality charts as static SVG without a browser:

```python
import plotly.graph_objects as go
import plotly.io as pio
import base64

# Create a chart
fig = go.Figure(data=[
    go.Bar(x=["Q1", "Q2", "Q3", "Q4"], y=[120, 180, 240, 200],
           marker_color="#3498db")
])
fig.update_layout(
    title="Quarterly Revenue",
    width=600, height=350,
    margin=dict(l=40, r=20, t=50, b=40),
    font=dict(family="Arial, sans-serif", size=12)
)

# Export as inline SVG (preferred)
svg_str = pio.to_image(fig, format="svg").decode("utf-8")

# Or as base64 PNG
png_b64 = base64.b64encode(
    pio.to_image(fig, format="png", scale=2)
).decode("utf-8")
```

Then embed in your HTML template:

```html
<!-- SVG (preferred — sharp at any resolution) -->
<div class="chart">{svg_str}</div>

<!-- Or base64 PNG -->
<div class="chart">
  <img src="data:image/png;base64,{png_b64}"
       alt="Quarterly Revenue"
       style="width:100%; max-width:600px;" />
</div>
```

> Plotly requires the `kaleido` package for static export: `pip install plotly kaleido`

### Other rendering options

| Approach | Description |
|----------|-------------|
| **Server-side SVG** | Generate SVG strings with D3 (Node.js) or matplotlib (Python) and embed inline |
| **JS library export** | Use Chart.js `.toBase64Image()`, Plotly `.toImage()`, or D3 `.node().outerHTML` before sending HTML to the API |
| **Static image API** | Use services like QuickChart.io to generate chart images via URL |
| **matplotlib / seaborn** | Save as SVG with `plt.savefig(buf, format='svg')` and embed the SVG string |

### Chart issues

- A `<canvas>` stays blank because it requires JavaScript. Use SVG or `<img>` instead.
- SVG `<text>` elements do not inherit document fonts. Set `font-family` and `font-size` explicitly.
- Use `viewBox` for responsive SVG scaling instead of fixed pixel dimensions.
- Wrap charts in a container with `break-inside: avoid` so they do not split across pages.
- Use high-contrast print colors instead of transparency or subtle gradients.

---

## PDF bookmarks

PDF bookmarks create a clickable outline in the viewer sidebar. Use `-pdfmcp-bookmark-*` properties to build it from document headings:

```css
/* Map headings to bookmark levels */
h1 { -pdfmcp-bookmark-level: 1; -pdfmcp-bookmark-state: open; }
h2 { -pdfmcp-bookmark-level: 2; -pdfmcp-bookmark-state: closed; }
h3 { -pdfmcp-bookmark-level: 3; }

/* Custom label text */
h1 { -pdfmcp-bookmark-label: content(text); }

/* Custom label for appendix sections */
.appendix h2 {
  -pdfmcp-bookmark-level: 1;
  -pdfmcp-bookmark-label: "Appendix: " content(text);
}

/* Exclude decorative headings */
.no-bookmark { -pdfmcp-bookmark-level: none; }
```

| Property | Values | Description |
|----------|--------|-------------|
| `-pdfmcp-bookmark-level` | `none`, `1`-`6` | Depth in the PDF outline |
| `-pdfmcp-bookmark-label` | `content(text)`, `content(before)`, `content()`, custom string | Display text |
| `-pdfmcp-bookmark-state` | `open`, `closed` | Whether children are expanded |

---

## Cross-references

Use `target-counter()` to insert the page number of an element by its `id` in a "see page X" reference or table of contents:

```css
a.page-ref::after {
  content: " (page " target-counter(attr(href), page) ")";
}
```

```html
<p>For more details, see <a class="page-ref" href="#results">Results</a>.</p>
<!-- Renders: "For more details, see Results (page 12)." -->
```

---

## Footnotes

CSS footnotes automatically move marked content to the bottom of the current page with auto-numbering:

```css
.footnote { float: footnote; }

::footnote-call {
  content: counter(footnote);
  font-size: 0.7em;
  vertical-align: super;
  line-height: none;
}

::footnote-marker {
  content: counter(footnote) ". ";
  font-weight: bold;
}

/* Optional rule above footnotes */
@page {
  @footnote {
    border-top: 1px solid #ccc;
    padding-top: 0.5em;
  }
}
```

```html
<p>Studies confirm this finding.<span class="footnote">Smith et al., 2024.</span></p>
```

The footnote text moves to the page bottom with automatic superscript numbering in both places.

---

## Hyphenation

Automatic hyphenation improves justified text by breaking long words at syllable boundaries:

```css
body {
  hyphens: auto;
  -webkit-hyphens: auto;
}
```

**Requirement:** Set the `lang` attribute on `<html>` for the hyphenation dictionary to load:

```html
<html lang="en">
```

Combine it with `text-align: justify` to reduce uneven spacing in justified paragraphs.

---

## Preserve backgrounds

Background colors are stripped by default in print rendering. Force them to appear with `print-color-adjust`:

```css
body {
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

/* Or target specific elements */
.colored-badge {
  background: #e74c3c;
  color: white;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
```

---

## Box decoration across pages

When an element with borders or backgrounds splits across a page boundary, `box-decoration-break: clone` repeats the full decoration on each fragment:

```css
.info-box {
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  border: 2px solid #3498db;
  padding: 1em;
  background: #f0f8ff;
}
```

Without `clone` (the default `slice` behavior), the top fragment loses its bottom border and the bottom fragment loses its top border at the page break.

---

## Table of contents

Combine `leader()` and `target-counter()` to create classic TOC entries with dot fills:

```css
.toc a {
  text-decoration: none;
  color: inherit;
  display: block;
}

.toc a::after {
  content: leader(".") target-counter(attr(href), page);
}

.toc .level-2 { padding-left: 1.5em; }
.toc .level-3 { padding-left: 3em; }
```

```html
<nav class="toc">
  <a class="level-1" href="#intro">Introduction</a>
  <a class="level-2" href="#background">Background</a>
  <a class="level-1" href="#methods">Methods</a>
</nav>
```

This renders as:

```
Introduction ............ 1
  Background ............ 3
Methods ................. 5
```

You can use different fill characters: `leader(".")`, `leader("-")`, `leader("_")`, or `leader(" ")`.

---

## CSS support

Paged PDF rendering supports a subset of CSS:

| Feature | Support | Notes |
|---------|---------|-------|
| **CSS Grid** | Supported for simple cases | Test complex grids and page breaks |
| **Flexbox** | Partial | Basic flex works; avoid for page-level structure |
| **CSS Variables** | Supported | `var(--color)` works normally |
| **Gradients** | Supported | `linear-gradient`, `radial-gradient` |
| **`calc()`** | Supported | Works in all contexts |
| **2D Transforms** | Supported | `rotate()`, `scale()`, `translate()` |
| **3D Transforms** | Not supported | No `perspective`, `rotateX/Y/Z` |
| **Box Shadow** | Not supported | Use borders or background colors instead |
| **Border Radius** | Supported | Works normally |
| **Opacity** | Supported | Works normally |
| **Animations** | Not supported | Static rendering only |
| **JavaScript** | Not executed | No `<script>`, no dynamic rendering |
| **`position: fixed`** | Supported | Fixed elements repeat across pages |
| **`position: sticky`** | Not supported | Use `thead { display: table-header-group; }` |
| **Viewport units** | Not supported | No `vw`/`vh`; use `mm`, `cm`, `in`, `pt` |

Use block and inline layout, floats, tables, or basic flexbox. Use CSS Paged Media properties such as `@page`, `break-*`, and margin boxes for print behavior.

---

## Invoice example

This invoice combines the page, font, table, and print rules from the guide:

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Invoice #12345</title>
  <style>
    @page {
      size: A4;
      margin: 2cm 1.5cm;

      @top-right {
        content: "Invoice #12345";
        font-size: 9pt;
        color: #666;
      }

      @bottom-center {
        content: "Page " counter(page) " of " counter(pages);
        font-size: 8pt;
        color: #999;
      }
    }

    @page :first {
      @top-right {
        content: none;
      }
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body {
      font-family: 'Helvetica Neue', Arial, sans-serif;
      font-size: 11pt;
      line-height: 1.5;
      color: #333;
    }

    .header {
      display: flex;
      justify-content: space-between;
      margin-bottom: 2cm;
      page-break-inside: avoid;
    }

    .company-info h1 {
      font-size: 24pt;
      color: #2563eb;
      margin-bottom: 0.5em;
    }

    .invoice-details {
      text-align: right;
    }

    .invoice-number {
      font-size: 14pt;
      font-weight: bold;
    }

    .addresses {
      display: flex;
      justify-content: space-between;
      margin-bottom: 1.5cm;
    }

    .address-block {
      width: 45%;
    }

    .address-block h3 {
      font-size: 10pt;
      text-transform: uppercase;
      color: #666;
      margin-bottom: 0.5em;
      border-bottom: 1px solid #ddd;
      padding-bottom: 0.25em;
    }

    table {
      width: 100%;
      border-collapse: collapse;
      margin-bottom: 1cm;
    }

    thead {
      display: table-header-group;
    }

    th {
      background: #f8f9fa;
      font-weight: 600;
      text-align: left;
      padding: 12px;
      border-bottom: 2px solid #dee2e6;
    }

    td {
      padding: 12px;
      border-bottom: 1px solid #eee;
    }

    tr {
      page-break-inside: avoid;
    }

    .text-right {
      text-align: right;
    }

    .totals {
      margin-left: auto;
      width: 250px;
      page-break-inside: avoid;
    }

    .totals-row {
      display: flex;
      justify-content: space-between;
      padding: 8px 0;
      border-bottom: 1px solid #eee;
    }

    .totals-row.total {
      font-weight: bold;
      font-size: 14pt;
      border-bottom: 2px solid #333;
      border-top: 2px solid #333;
      padding: 12px 0;
    }

    .notes {
      margin-top: 2cm;
      padding: 1cm;
      background: #f8f9fa;
      border-radius: 4px;
      page-break-inside: avoid;
    }

    .notes h4 {
      margin-bottom: 0.5em;
    }

    @media print {
      .no-print {
        display: none;
      }
    }
  </style>
</head>
<body>
  <div class="header">
    <div class="company-info">
      <h1>ACME Corp</h1>
      <p>123 Business Street</p>
      <p>City, State 12345</p>
      <p>contact@acme.com</p>
    </div>
    <div class="invoice-details">
      <p class="invoice-number">Invoice #12345</p>
      <p>Date: January 15, 2025</p>
      <p>Due: February 15, 2025</p>
    </div>
  </div>

  <div class="addresses">
    <div class="address-block">
      <h3>Bill To</h3>
      <p><strong>Client Company</strong></p>
      <p>456 Client Avenue</p>
      <p>City, State 67890</p>
    </div>
    <div class="address-block">
      <h3>Ship To</h3>
      <p><strong>Client Company</strong></p>
      <p>456 Client Avenue</p>
      <p>City, State 67890</p>
    </div>
  </div>

  <table>
    <thead>
      <tr>
        <th>Description</th>
        <th>Qty</th>
        <th class="text-right">Unit Price</th>
        <th class="text-right">Amount</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Professional Services</td>
        <td>10</td>
        <td class="text-right">$150.00</td>
        <td class="text-right">$1,500.00</td>
      </tr>
      <tr>
        <td>Software License</td>
        <td>1</td>
        <td class="text-right">$499.00</td>
        <td class="text-right">$499.00</td>
      </tr>
      <tr>
        <td>Support Package</td>
        <td>1</td>
        <td class="text-right">$99.00</td>
        <td class="text-right">$99.00</td>
      </tr>
    </tbody>
  </table>

  <div class="totals">
    <div class="totals-row">
      <span>Subtotal</span>
      <span>$2,098.00</span>
    </div>
    <div class="totals-row">
      <span>Tax (10%)</span>
      <span>$209.80</span>
    </div>
    <div class="totals-row total">
      <span>Total</span>
      <span>$2,307.80</span>
    </div>
  </div>

  <div class="notes">
    <h4>Notes</h4>
    <p>Payment is due within 30 days. Please include invoice number with your payment.</p>
  </div>
</body>
</html>
```

---

## Rendering notes

### General

1. Preview PDF output throughout development.
2. Use `pt` for font sizes and `cm` or `in` for margins.
3. Keep layouts simple enough to paginate predictably.
4. Include fallback fonts and graceful degradation.

### Performance

- Inline critical CSS to avoid external stylesheet requests.
- Size images for their intended output; use base64 for small graphics.
- Limit external resources because each one adds latency.

### Accessibility

- Use enough contrast for text to remain readable in print.
- Keep body text at 10pt or larger.
- Use semantic HTML to preserve meaningful content flow.

### Common issues

| Issue | Solution |
|-------|----------|
| Content cut off at page edges | Set adequate page margins |
| Headers/footers overlap content | Ensure margin boxes don't exceed margin size |
| Tables split awkwardly | Use `display: table-header-group` on `thead` |
| Images overflow pages | Set `max-width: 100%` and `page-break-inside: avoid` |
| Fonts not rendering | Use web-safe fonts or embed via @font-face |
| Colors not printing | Add `print-color-adjust: exact` |

---

## Quick reference

### CSS properties

```css
/* Page setup */
@page { size: A4; margin: 2cm; }

/* Page breaks */
page-break-before: always | avoid | auto;
page-break-after: always | avoid | auto;
page-break-inside: avoid | auto;

/* Modern alternatives */
break-before: page | avoid | auto;
break-after: page | avoid | auto;
break-inside: avoid | auto;

/* Paragraph control */
orphans: 3;
widows: 3;

/* Table headers */
thead { display: table-header-group; }

/* Color printing */
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
```

### Page sizes

| Size | Dimensions (mm) | Dimensions (inches) |
|------|-----------------|---------------------|
| A4 | 210 x 297 | 8.27 x 11.69 |
| A5 | 148 x 210 | 5.83 x 8.27 |
| Letter | 216 x 279 | 8.5 x 11 |
| Legal | 216 x 356 | 8.5 x 14 |
| Tabloid | 279 x 432 | 11 x 17 |

---

## Related documentation

- [HTML to PDF](https://pdf-mcp.io/docs/api-html-to-pdf): convert HTML through the API
- [API reference](https://pdf-mcp.io/docs/api-reference): endpoint documentation
- [Quick start](https://pdf-mcp.io/docs/quick-start): make the first request in five minutes
