# @json-render/react-pdf

PDF document renderer. Turn JSON specs into PDFs using `@react-pdf/renderer`.

## Install

```bash
npm install @json-render/core @json-render/react-pdf
```

See the [React PDF example](https://github.com/vercel-labs/json-render/tree/main/examples/react-pdf) for a full working example.

## schema

The PDF element schema for document specs. Use with `defineCatalog` from core.

```typescript

const catalog = defineCatalog(schema, {
  components: standardComponentDefinitions,
});
```

## Render Functions

Server-side functions for producing PDF output. All accept a spec and optional `RenderOptions`.

```typescript

const buffer = await renderToBuffer(spec);

const stream = await renderToStream(spec);
stream.pipe(res);

await renderToFile(spec, './output.pdf');
```

### RenderOptions

```typescript
interface RenderOptions {
  registry?: ComponentRegistry;
  includeStandard?: boolean;  // default: true
  state?: Record<string, unknown>;
}
```

<table>
  <thead>
    <tr>
      <th>Option</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>registry</code></td>
      <td>Custom component map (merged with standard components)</td>
    </tr>
    <tr>
      <td><code>includeStandard</code></td>
      <td>Include built-in standard components (default: <code>true</code>)</td>
    </tr>
    <tr>
      <td><code>state</code></td>
      <td>Initial state for <code>$state</code> / <code>$cond</code> dynamic prop resolution</td>
    </tr>
  </tbody>
</table>

## defineRegistry

Create a type-safe component registry from a catalog. Components receive `{ props, children, emit, bindings, loading }`.

```tsx

const { registry } = defineRegistry(catalog, {
  components: {
    Badge: ({ props }) => (
      <View style={{ backgroundColor: props.color ?? '#e5e7eb', padding: 4, borderRadius: 4 }}>
        <Text style={{ fontSize: 10 }}>{props.label}</Text>
      </View>
    ),
  },
});

const buffer = await renderToBuffer(spec, { registry });
```

## createRenderer

Create a standalone renderer component wired to state, actions, and validation.

```typescript

const PDFRenderer = createRenderer(catalog, components);
```

```typescript
interface CreateRendererProps {
  spec: Spec | null;
  store?: StateStore;
  state?: Record<string, unknown>;
  onAction?: (actionName: string, params?: Record<string, unknown>) => void;
  onStateChange?: (changes: Array<{ path: string; value: unknown }>) => void;
  loading?: boolean;
  fallback?: ComponentRenderer;
}
```

When `store` is provided, `state` and `onStateChange` are ignored (controlled mode).

## Renderer

The main component that renders a spec to `@react-pdf/renderer` elements.

```typescript
interface RendererProps {
  spec: Spec | null;
  registry?: ComponentRegistry;
  includeStandard?: boolean;  // default: true
  loading?: boolean;
  fallback?: ComponentRenderer;
}
```

## Standard Components

### Document Structure

#### Document

Top-level PDF wrapper. Must be the root element. Children must be `Page` components.

```typescript
{
  title: string | null;
  author: string | null;
  subject: string | null;
}
```

#### Page

A page in the document with configurable size, orientation, and margins.

```typescript
{
  size: "A4" | "A3" | "A5" | "LETTER" | "LEGAL" | "TABLOID" | null;
  orientation: "portrait" | "landscape" | null;
  marginTop: number | null;
  marginBottom: number | null;
  marginLeft: number | null;
  marginRight: number | null;
  backgroundColor: string | null;
}
```

### Layout

#### View

Generic container with padding, margin, background, border, and flex alignment.

```typescript
{
  padding: number | null;
  paddingTop: number | null;
  paddingBottom: number | null;
  paddingLeft: number | null;
  paddingRight: number | null;
  margin: number | null;
  backgroundColor: string | null;
  borderWidth: number | null;
  borderColor: string | null;
  borderRadius: number | null;
  flex: number | null;
  alignItems: "flex-start" | "center" | "flex-end" | "stretch" | null;
  justifyContent: "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | null;
}
```

#### Row

Horizontal flex layout with optional wrapping.

```typescript
{
  gap: number | null;
  alignItems: "flex-start" | "center" | "flex-end" | "stretch" | null;
  justifyContent: "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | null;
  padding: number | null;
  flex: number | null;
  wrap: boolean | null;
}
```

#### Column

Vertical flex layout.

```typescript
{
  gap: number | null;
  alignItems: "flex-start" | "center" | "flex-end" | "stretch" | null;
  justifyContent: "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | null;
  padding: number | null;
  flex: number | null;
}
```

### Content

#### Heading

h1-h4 heading text with configurable color and alignment.

```typescript
{
  text: string;
  level: "h1" | "h2" | "h3" | "h4" | null;
  color: string | null;
  align: "left" | "center" | "right" | null;
}
```

#### Text

Body text with full styling control.

```typescript
{
  text: string;
  fontSize: number | null;
  color: string | null;
  align: "left" | "center" | "right" | null;
  fontWeight: "normal" | "bold" | null;
  fontStyle: "normal" | "italic" | null;
  lineHeight: number | null;
}
```

#### Image

Image from a URL with optional dimensions and fit.

```typescript
{
  src: string;
  width: number | null;
  height: number | null;
  objectFit: "contain" | "cover" | "fill" | "none" | null;
}
```

#### Link

Hyperlink with visible text.

```typescript
{
  text: string;
  href: string;
  fontSize: number | null;
  color: string | null;
}
```

### Data

#### Table

Data table with typed columns and string rows. Supports header styling and striped rows.

```typescript
{
  columns: { header: string; width?: string; align?: "left" | "center" | "right" }[];
  rows: string[][];
  headerBackgroundColor: string | null;
  headerTextColor: string | null;
  borderColor: string | null;
  fontSize: number | null;
  striped: boolean | null;
}
```

#### List

Ordered or unordered list.

```typescript
{
  items: string[];
  ordered: boolean | null;
  fontSize: number | null;
  color: string | null;
  spacing: number | null;
}
```

### Decorative

#### Divider

Horizontal line separator.

```typescript
{
  color: string | null;
  thickness: number | null;
  marginTop: number | null;
  marginBottom: number | null;
}
```

#### Spacer

Empty vertical space.

```typescript
{
  height: number | null;
}
```

### Page-Level

#### PageNumber

Renders current page number and total pages. Format uses `{pageNumber}` and `{totalPages}` placeholders.

```typescript
{
  format: string | null;    // default: "{pageNumber} / {totalPages}"
  fontSize: number | null;
  color: string | null;
  align: "left" | "center" | "right" | null;
}
```

## External Store (Controlled Mode)

Pass a `StateStore` to `StateProvider`, `JSONUIProvider`, or `createRenderer` for full control over state:

```tsx

const store = createStateStore({ invoice: { total: 100 } });
store.set("/invoice/total", 200);
```

When `store` is provided, `initialState` / `state` and `onStateChange` are ignored.

## Server-Safe Import

Import schema and catalog definitions without pulling in React or `@react-pdf/renderer`:

```typescript
```

## Sub-path Exports

<table>
  <thead>
    <tr>
      <th>Export</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>@json-render/react-pdf</code></td>
      <td>Full package: schema, renderer, components, render functions</td>
    </tr>
    <tr>
      <td><code>@json-render/react-pdf/server</code></td>
      <td>Schema and catalog definitions only (no React)</td>
    </tr>
    <tr>
      <td><code>@json-render/react-pdf/catalog</code></td>
      <td>Standard component definitions and types</td>
    </tr>
    <tr>
      <td><code>@json-render/react-pdf/render</code></td>
      <td>Server-side render functions only</td>
    </tr>
  </tbody>
</table>

## Types

<table>
  <thead>
    <tr>
      <th>Export</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>ReactPdfSchema</code></td>
      <td>Schema type for PDF specs</td>
    </tr>
    <tr>
      <td><code>ReactPdfSpec</code></td>
      <td>Spec type for PDF documents</td>
    </tr>
    <tr>
      <td><code>RenderOptions</code></td>
      <td>Options for render functions</td>
    </tr>
    <tr>
      <td><code>ComponentContext</code></td>
      <td>Typed component render function context</td>
    </tr>
    <tr>
      <td><code>ComponentFn</code></td>
      <td>Component render function type</td>
    </tr>
    <tr>
      <td><code>StandardComponentDefinitions</code></td>
      <td>Type of the standard component definitions object</td>
    </tr>
    <tr>
      <td><code>StandardComponentProps&lt;K&gt;</code></td>
      <td>Inferred props type for a standard component by name</td>
    </tr>
  </tbody>
</table>