# @json-render/image

Image renderer. Turn JSON specs into SVG and PNG images using [Satori](https://github.com/vercel/satori).

## Install

```bash
npm install @json-render/core @json-render/image
```

For PNG output, also install the optional peer dependency:

```bash
npm install @resvg/resvg-js
```

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

## schema

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

```typescript

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

## Render Functions

Server-side functions for producing image output. Both accept a spec and optional `RenderOptions`.

```typescript

const svg = await renderToSvg(spec, { fonts });

const png = await renderToPng(spec, { fonts });
await writeFile('output.png', png);
```

### RenderOptions

```typescript
interface RenderOptions {
  registry?: ComponentRegistry;
  includeStandard?: boolean;  // default: true
  state?: Record<string, unknown>;
  fonts?: SatoriOptions['fonts'];
  width?: number;
  height?: number;
}
```

<table>
  <thead>
    <tr>
      <th>Option</th>
      <th>Type</th>
      <th>Default</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>fonts</code></td>
      <td><code>{"SatoriOptions['fonts']"}</code></td>
      <td><code>[]</code></td>
      <td>Font data for text rendering (required for meaningful output)</td>
    </tr>
    <tr>
      <td><code>width</code></td>
      <td><code>number</code></td>
      <td>Frame prop</td>
      <td>Override the output image width</td>
    </tr>
    <tr>
      <td><code>height</code></td>
      <td><code>number</code></td>
      <td>Frame prop</td>
      <td>Override the output image height</td>
    </tr>
    <tr>
      <td><code>registry</code></td>
      <td><code>{"Record<string, ComponentRenderer>"}</code></td>
      <td><code>{"{}"}</code></td>
      <td>Custom component map (merged with standard components)</td>
    </tr>
    <tr>
      <td><code>includeStandard</code></td>
      <td><code>boolean</code></td>
      <td><code>true</code></td>
      <td>Include built-in standard components</td>
    </tr>
    <tr>
      <td><code>state</code></td>
      <td><code>{"Record<string, unknown>"}</code></td>
      <td><code>{"{}"}</code></td>
      <td>Initial state for <code>$state</code> / <code>$cond</code> dynamic prop resolution</td>
    </tr>
  </tbody>
</table>

## Standard Components

### Root

#### Frame

Root image container. Defines the output image dimensions and background. Must be the root element.

```typescript
{
  width: number;
  height: number;
  backgroundColor: string | null;
  padding: number | null;
  display: "flex" | "none" | null;
  flexDirection: "row" | "column" | null;
  alignItems: "flex-start" | "center" | "flex-end" | "stretch" | null;
  justifyContent: "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | null;
}
```

### Layout

#### Box

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

```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;
  width: number | string | null;
  height: number | string | null;
  alignItems: "flex-start" | "center" | "flex-end" | "stretch" | null;
  justifyContent: "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | null;
  flexDirection: "row" | "column" | null;
  position: "relative" | "absolute" | null;
  top: number | null;
  left: number | null;
  right: number | null;
  bottom: number | null;
  overflow: "visible" | "hidden" | 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

Heading text at various levels. h1 is largest, h4 is smallest.

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

#### Text

Body text with configurable size, color, weight, and alignment.

```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;
  letterSpacing: number | string | null;
  textDecoration: "none" | "underline" | "line-through" | null;
}
```

#### Image

Image from a URL with optional dimensions and fit.

```typescript
{
  src: string;
  width: number | null;
  height: number | null;
  borderRadius: number | null;
  objectFit: "contain" | "cover" | "fill" | "none" | 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;
}
```

## Catalog Definitions

Pre-built definitions for creating image catalogs:

```typescript

const catalog = defineCatalog(schema, {
  components: {
    ...standardComponentDefinitions,
    // Add custom components
  },
});
```

## Server-Safe Import

Import schema and catalog definitions without pulling in React or Satori:

```typescript
```

## Sub-path Exports

<table>
  <thead>
    <tr>
      <th>Export</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>@json-render/image</code></td>
      <td>Full package: schema, renderer, components, render functions</td>
    </tr>
    <tr>
      <td><code>@json-render/image/server</code></td>
      <td>Schema and catalog definitions only (no React or Satori)</td>
    </tr>
    <tr>
      <td><code>@json-render/image/catalog</code></td>
      <td>Standard component definitions and types</td>
    </tr>
    <tr>
      <td><code>@json-render/image/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>ImageSchema</code></td>
      <td>Schema type for image specs</td>
    </tr>
    <tr>
      <td><code>ImageSpec</code></td>
      <td>Spec type for image output</td>
    </tr>
    <tr>
      <td><code>RenderOptions</code></td>
      <td>Options for render functions</td>
    </tr>
    <tr>
      <td><code>ComponentRenderProps</code></td>
      <td>Props passed to component render functions</td>
    </tr>
    <tr>
      <td><code>ComponentRenderer</code></td>
      <td>Component render function type</td>
    </tr>
    <tr>
      <td><code>ComponentRegistry</code></td>
      <td>Map of component names to render functions</td>
    </tr>
    <tr>
      <td><code>StandardComponentDefinitions</code></td>
      <td>Type of the standard component definitions object</td>
    </tr>
    <tr>
      <td><code>StandardComponentProps{'<K>'}</code></td>
      <td>Inferred props type for a standard component by name</td>
    </tr>
  </tbody>
</table>