# PDF Generation

Generate PDFs with configurable page size, margins, orientation, and background printing. nokap leverages Chrome's built-in print-to-PDF functionality through the DevTools Protocol, giving you access to the same rendering engine used for on-screen display. This page walks through all available PDF options including standard paper sizes, custom dimensions, margin control, and header/footer configuration.

## Basic PDF Capture

nokap determines the output format from the file extension. Any [webshot()](../reference/webshot.md#nokap.webshot) or [from_html()](../reference/from_html.md#nokap.from_html) call with a `.pdf` output extension generates a PDF instead of a raster screenshot (no separate function or mode switch is required):

``` python
import nokap

nokap.webshot("https://example.com", "page.pdf")
```

## Page Size

For full-page PDFs, the `page_size` parameter controls the paper dimensions. This affects how content is paginated and how much fits on each page:

``` python
nokap.webshot("https://example.com", "a4.pdf", page_size="a4")
nokap.webshot("https://example.com", "letter.pdf", page_size="letter")
```

Available page sizes:

| Size      | Dimensions (inches) |
|-----------|---------------------|
| `letter`  | 8.5 × 11            |
| `legal`   | 8.5 × 14            |
| `tabloid` | 11 × 17             |
| `ledger`  | 17 × 11             |
| `a0`      | 33.1 × 46.8         |
| `a1`      | 23.4 × 33.1         |
| `a2`      | 16.5 × 23.4         |
| `a3`      | 11.7 × 16.5         |
| `a4`      | 8.27 × 11.7         |
| `a5`      | 5.83 × 8.27         |
| `a6`      | 4.13 × 5.83         |

## Margins

Set page margins in inches. A single value applies to all sides:

``` python
# 1-inch margins on all sides
nokap.webshot("https://example.com", "page.pdf", margins=1.0)

# No margins
nokap.webshot("https://example.com", "page.pdf", margins=0)
```

For asymmetric margins, pass a 4-tuple of `(top, right, bottom, left)`:

``` python
nokap.webshot(
    "https://example.com",
    "page.pdf",
    margins=(1.0, 0.75, 1.0, 0.75),  # top, right, bottom, left
)
```

The default margin is `0.5` inches on all sides.

## Orientation

By default, PDFs render in portrait orientation. Use `landscape=True` to rotate the page, which is useful for wide content like data tables or charts that benefit from extra horizontal space:

``` python
nokap.webshot("https://example.com", "wide.pdf", landscape=True)
```

## Print Background

Chrome's print-to-PDF follows the same convention as physical printing: CSS background colors and images are omitted by default to save ink. For web content where backgrounds are part of the design (styled tables, colored headings, etc.), enable them explicitly:

``` python
nokap.webshot(
    "https://example.com",
    "styled.pdf",
    print_background=True,
)
```

## A Note on Scale

Unlike raster screenshots where `zoom` controls pixel density, PDFs are a vector format: text and graphics are stored as scalable geometry, not pixels. This means PDFs always render at the highest possible resolution regardless of any scale setting.

> **Note**
>
> The `zoom` parameter is ignored for PDF output. Use `zoom` only for raster screenshots (PNG, JPEG, WebP) where it controls output pixel density and sharpness on HiDPI displays.

## Element-Bounded PDF

One of nokap's most powerful features is the ability to produce a PDF that's sized exactly to a specific element rather than a standard paper size. When you combine a `.pdf` output with a `selector`, nokap produces a tightly-bounded PDF with the paper dimensions matched to the element's bounding box. The text remains fully selectable and searchable (unlike a raster screenshot embedded in a PDF).

This is the feature to reach for when you need to place a table or chart into a presentation (Keynote, PowerPoint, Google Slides) at vector quality, or when you want a compact PDF that contains only the content you care about.

``` python
# Capture just the table as a tight-fit PDF
nokap.webshot("report.html", "table.pdf", selector="table", expand=10)
```

Under the hood, the element-bounded PDF works by:

1.  Measuring the element's bounding box in CSS pixels
2.  Setting the PDF paper size to exactly match those dimensions (converted to inches)
3.  Injecting print-specific CSS to isolate the element and hide surrounding content
4.  Rendering via Chrome's `Page.printToPDF` with zero page margins

The result is a single-page PDF where the page boundary coincides with the element boundary (plus any `expand` padding you specify).

### With [from_html()](../reference/from_html.md#nokap.from_html)

For packages that generate HTML programmatically (like Great Tables, Plotly, or custom report builders) [from_html()](../reference/from_html.md#nokap.from_html) is the natural entry point. Pass the HTML string and a selector to extract just the relevant element as a vector PDF:

``` python
from great_tables import GT, exibble

html = GT(exibble).as_raw_html(make_page=True, all_important=True)

# Tight PDF of just the table (useful for slides)
nokap.from_html(html, "table.pdf", selector="table", expand=5)
```

### Expand / Padding

The `expand` parameter adds even whitespace around the element in the PDF. This is purely a visual affordance and it gives the content some breathing room so it doesn't feel jammed against the edges when placed into a slide or document:

``` python
# No padding: PDF edges touch the table
nokap.from_html(html, "tight.pdf", selector="table")

# 20px breathing room on all sides
nokap.from_html(html, "padded.pdf", selector="table", expand=20)

# Asymmetric padding
nokap.from_html(html, "asym.pdf", selector="table", expand=(10, 20, 10, 20))
```

### Backgrounds

Element-bounded PDFs include CSS backgrounds by default. This is intentional: when you're exporting a styled table or chart, the background colors, borders, and shading are typically essential to the visual design. Full-page PDFs follow Chrome's printing convention and omit backgrounds unless you explicitly set `print_background=True`.

### Wide Tables

A common challenge with table-to-PDF export is that wide tables get clipped when they exceed the viewport width. nokap handles this automatically: it detects when an element's natural width is being constrained by the viewport, temporarily widens the viewport to let the content expand, then sizes the PDF paper to fit the full element. No manual `vwidth` adjustment is needed (even very wide tables with dozens of columns render completely).

``` python
import pandas as pd
from great_tables import GT

# A 20-column table that's wider than the default 992px viewport
df = pd.DataFrame({f"col_{i}": [f"val_{i}"] for i in range(20)})
html = GT(df).as_raw_html(make_page=True, all_important=True)

# Automatically expands to fit all columns
nokap.from_html(html, "wide_table.pdf", selector="table", expand=5)
```

## Full-Page PDF vs Element-Bounded PDF

To summarize the two PDF modes and when to reach for each:

|  | Full-Page PDF | Element-Bounded PDF |
|----|----|----|
| **Trigger** | `.pdf` output, no selector | `.pdf` output + `selector` |
| **Paper size** | Standard (letter, A4, etc.) | Sized to element |
| **Content** | Entire page | Just the selected element |
| **Backgrounds** | Off by default | On by default |
| **Use case** | Print a document | Export a table/chart for slides |

Both modes produce true vector PDFs with selectable text. The choice comes down to whether you want a traditional paginated document or a compact, element-sized artifact for embedding elsewhere.

## PDF from HTML Strings

Just as with screenshots, you can generate a full-page PDF directly from an HTML string using [from_html()](../reference/from_html.md#nokap.from_html). This is convenient for generating documents from templates or programmatic HTML without writing to a temporary file yourself:

``` python
html = """
<html>
<body>
  <h1>Invoice #1234</h1>
  <table>
    <tr><td>Widget</td><td>$10.00</td></tr>
    <tr><td>Gadget</td><td>$25.00</td></tr>
    <tr><th>Total</th><th>$35.00</th></tr>
  </table>
</body>
</html>
"""

nokap.from_html(html, "invoice.pdf", page_size="a4", margins=1.0)
```

## Complete Example

Here's a full-page PDF capture with all common options specified explicitly, showing how the parameters work together:

``` python
nokap.webshot(
    "report.html",
    "report.pdf",
    page_size="a4",
    margins=(1.0, 0.75, 1.0, 0.75),
    landscape=False,
    print_background=True,
)
```

And an element-bounded PDF workflow for exporting a styled table to a presentation:

``` python
from great_tables import GT, exibble

html = GT(exibble).as_raw_html(make_page=True, all_important=True)
nokap.from_html(html, "table_for_slides.pdf", selector="table", expand=10)
```

## Next Steps

This page covered both full-page and element-bounded PDF generation from standard paper sizes and margins to the tightly-fitted, selector-driven PDFs that are ideal for embedding in presentations. Because PDFs are vector format, you get crisp text at any zoom level without worrying about pixel density.

- [The CLI](cli.md): Generate PDFs from the command line
- [Integration with Great Tables](great-tables-integration.md): Export tables as images or PDFs

All of the PDF options covered here are also available via the command-line interface, making it easy to integrate nokap into scripts and automation pipelines.
