# textRenderer

Render Paradoc artifacts to text-based formats

Canonical URL: https://docs.paradoc.dev/sdk/renderers/text/

Render forms to HTML, Markdown, or plain text. Templates keep the familiar
block markers (`{{#if}}`, `{{#unless}}`, `{{else}}`, `{{#each}}`), and
everything inside a marker is an [artifact expression](/concepts/logic): the
same language, types, functions, and decimal precision as field conditions and
computed values. Nothing runs arbitrary JavaScript. Values printed from a path
are formatted from their schema types.

## Example

```ts
import { p } from '@paradoc/core'
import { textRenderer } from '@paradoc/render/text'

const form = p.form({
  name: 'receipt',
  fields: {
    customer: { type: 'text', label: 'Customer' },
    total: { type: 'money', label: 'Total' },
    date: { type: 'date', label: 'Date' }
  },
  layers: {
    html: {
      kind: 'inline',
      mimeType: 'text/html',
      text: `
        <h1>Receipt</h1>
        <p>Customer: {{fields.customer}}</p>
        <p>Total: {{fields.total}}</p>
        <p>Date: {{fields.date}}</p>
      `
    }
  },
  defaultLayer: 'html'
})

// Fill the form with data
const filled = form.fill({
  fields: {
    customer: 'Acme Corp',
    total: { amount: 250.00, currency: 'USD' },
    date: '2024-03-15'
  }
})

// Render the filled form
const output = await filled.render({
  renderer: textRenderer()
})
// Output:
// <h1>Receipt</h1>
// <p>Customer: Acme Corp</p>
// <p>Total: $250.00</p>
// <p>Date: Mar 15, 2024</p>
```

## Import

From the unified package's text entry point:

```ts
import { textRenderer } from '@paradoc/render/text'
```

## API

### textRenderer()

Factory function that creates a configured text renderer instance.

```ts
function textRenderer(options?: TextRendererOptions): ParadocRenderer<
  RendererLayer & { type: 'text'; content: string },
  string
>
```

#### Parameters

| Name | Type | Description |
| --- | --- | --- |
| `options?` | `TextRendererOptions` | Configuration options |

#### TextRendererOptions

| Name | Type | Description |
| --- | --- | --- |
| `formatter?` | `Formatter` | Formatter used for every declared artifact value. |
| `progressive?` | `FormatterProgressivePolicy` | Text for missing and incomplete values in a preview. |
| `signatureOptions?` | `TextSignatureOptions` | Options for signature and initials template marker rendering. |
| `expressions?` | `{ functions?, signatures? }` | Host-configured expression functions templates can call, with their signatures. A template cannot call a function without a signature. |

#### TextSignatureOptions

| Name | Type | Description |
| --- | --- | --- |
| `format?` | `'text' \| 'html' \| 'markdown'` | Output format for signatures. Defaults from the layer MIME type, or to "text". |
| `placeholder?` | `{ signature?, initials?, signatureDate?, capacity?, printedName? }` | Placeholder text/function for unsigned signing marks. |
| `captured?` | `{ signature?, initials?, signatureDate?, capacity?, printedName? }` | Text/function for captured signing marks. |
| `altText?` | `string` | Alt text for signature images (html format only). |
| `cssClass?` | `string` | CSS class for signature images (html format only). |

#### Returns

Returns an `ParadocRenderer` object with:

| Name | Type | Description |
| --- | --- | --- |
| `id` | `"text"` | Renderer identifier |

**Methods**

| Name | Type | Description |
| --- | --- | --- |
| `render` | `(request: RenderRequest) => string` | Render function that returns the populated template string |

### renderText()

Low-level function for direct template rendering without the full renderer interface.

```ts
function renderText(options: {
  template: string
  data: Record<string, unknown>
  form?: Form
  formatter?: Formatter
  progressive?: FormatterProgressivePolicy
  signatureOptions?: TextSignatureOptions
  expressions?: TemplateExpressionOptions
  layer?: string
  mimeType?: string
}): string
```

#### Parameters

| Name | Type | Description |
| --- | --- | --- |
| `template` | `string` | Paradoc template string |
| `data` | `Record<string, unknown>` | Data object to populate the template |
| `form?` | `Form` | Form schema for automatic field-aware formatting |
| `formatter?` | `Formatter` | Formatter used for declared artifact values. |
| `progressive?` | `FormatterProgressivePolicy` | Text for missing and incomplete values. |
| `signatureOptions?` | `TextSignatureOptions` | Options for signature and initials rendering |
| `expressions?` | `TemplateExpressionOptions` | The expression context (`context`), or configured `functions` and their `signatures`. Artifact renders supply the artifact context. |
| `layer?` | `string` | Layer key, named in template errors |
| `mimeType?` | `string` | Layer media type. `text/html` enables HTML escaping and selects HTML signature output. |

#### Returns

Returns the rendered template as a `string`. A template that cannot render throws a `TemplateError` with the layer, the line and column, and the expression.

### checkTextTemplate()

Checks every expression in a template against a type environment, without rendering. `validate()` runs it on inline layers and `validateLayers()` on file layers.

```ts
function checkTextTemplate(template: string, env: TypeEnv): TemplateDiagnostic[]
```

Each diagnostic has a `code`, a `message`, the `expression`, and its `position` (`line` and `column`).

### textTemplateSigningDirectives()

Lists the signing directives a template writes, including those inside blocks, without rendering. Each entry has the `directive` name and, when written as a string literal, its `location`. Returns `undefined` when the template's markers do not parse. `validate()` and `validateLayers()` use it to check that each `'flow'` signature slot is placed.

```ts
function textTemplateSigningDirectives(template: string): SigningDirectiveUse[] | undefined
```

## Automatic Formatting

When a form schema is provided, field values are automatically serialized based on their types:

```ts
const form = p.form({
  name: 'demo',
  fields: {
    price: { type: 'money' },
    percentage: { type: 'percentage' },
    phone: { type: 'phone' }
  },
  layers: {
    text: { kind: 'inline', mimeType: 'text/plain', text: '{{fields.price}} {{fields.percentage}} {{fields.phone}}' }
  }
})

const filled = form.fill({
  fields: {
    price: { amount: 99.99, currency: 'USD' },
    percentage: 15,
    phone: { number: '+14155551234' }
  }
})

const output = await filled.render({
  renderer: textRenderer()
})
// output: "$99.99 15% +14155551234"
```

## Custom Serializers

Pass a formatter for a precise locale or formatting policy:

```ts
import { textRenderer } from '@paradoc/render/text'
import { createFormatter } from '@paradoc/format'

const renderer = textRenderer({
  formatter: createFormatter({ locale: 'de-DE' })
})

// USD formatted as "99,99 $" instead of "$99.99"
```

## Values in templates

A template names each value by its path, such as `{{fields.buyerFullName}}`. A text layer takes no `bindings` or `bindingsFrom`, and validation rejects them there: bindings belong to [PDF layers](/sdk/renderers/pdf), where they map AcroForm field names to Paradoc paths.

## Signature and Initials Directives

Signing directives place signature and initials marks. They are template directives rather than expression functions, and their arguments are expressions.

### Template Syntax

```text
{{signature(parties.landlord, "final-sig")}}  - A signature placeholder, or the captured mark
{{initials(parties.landlord, "page-init")}}   - An initials placeholder, or the captured mark
{{signature("final-sig")}}                    - Inside a party or signatories loop: the current row signs
```

`signatureDate`, `capacity`, and `printedName` take the same arguments. The location (for example "final-sig" or "page-3-init") identifies where in the document the mark appears. The party role, party ID, and signer ID come from the party given first, or from the loop row. A person with no signatories signs for itself: its signer ID is its party ID. An organization with no signatories has no signer.

### Example with Signatures

```ts
const form = p.form({
  name: 'lease',
  fields: { /* ... */ },
  parties: {
    tenant: { label: 'Tenant', signature: { required: true } },
    landlord: { label: 'Landlord', signature: { required: true } }
  },
  layers: {
    html: {
      kind: 'inline',
      mimeType: 'text/html',
      text: `
        <h1>Lease Agreement</h1>

        <h2>Tenants</h2>
        {{#each parties.tenant}}
        <p>Name: {{item.name}}</p>
        <p>Signature: {{signature("final-sig")}}</p>
        <p>Initials: {{initials("page-init")}}</p>
        {{/each}}

        <h2>Landlord</h2>
        {{#each parties.landlord.signatories}}
        <p>{{item.signer.person.name}}, {{item.capacity}}</p>
        <p>Signature: {{signature("final-sig")}}</p>
        {{/each}}
      `
    }
  }
})

// An HTML layer prints HTML signing marks
const output = await form.fill(data).render({ layer: 'html' })
```

### Output Formats

`signature` and `initials` print in the format the layer's MIME type implies: `html` for `text/html`, `markdown` for `text/markdown`, else `text`. `signatureOptions.format` overrides it. An HTML mark prints as is inside `{{ }}`; captured text that `capacity` and `printedName` print is escaped as usual. The table shows `signature`; `initials` prints `[INITIALS]` and `[Initialed]` the same way.

| Format     | Unsigned                                                        | Signed, no image                                          | Signed, with image                                                    |
| ---------- | --------------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |
| `text`     | `[SIGNATURE]`                                                   | `[Signed]`                                                | `[Signed]`                                                            |
| `html`     | `<span class="signature-placeholder" data-…>[SIGNATURE]</span>` | `<span class="signature-captured" data-…>[Signed]</span>` | `<img src="data:…" alt="Signature" class="signature-image" data-… />` |
| `markdown` | `_[SIGNATURE]_`                                                 | `_[Signed]_`                                              | `![Signature](data:…)`                                                |

`signatureDate` prints `[DATE]` before the signature at the same location is captured, and the capture's `YYYY-MM-DD` date after. In the `html` format it is wrapped in a `signature-date-placeholder` or `signature-date` span, except a `captured.signatureDate` value, which prints as given.

### Capacity and Printed Name

`capacity` and `printedName` print the signer's details as plain text in every format. They read the signatory: the loop row, or the first signatory of the party given first.

| Directive                                            | Before capture                                                                                             | After a capture at the location                                                                        |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `{{capacity(parties.landlord, "landlord-cap")}}`     | The signatory's `capacity` (for example `Managing Member`), else `placeholder.capacity`, else `[CAPACITY]` | `captured.capacity`, else the captured text, else the signatory's `capacity`, else `[CAPACITY]`        |
| `{{printedName(parties.landlord, "landlord-name")}}` | The signer's `person.name`, else `placeholder.printedName`, else `[PRINTED NAME]`                          | `captured.printedName`, else the captured text, else the signer's `person.name`, else `[PRINTED NAME]` |

A capture counts only when its type matches: a `capacity` capture for `capacity`, a `printed_name` capture for `printedName`. Signing records them with `captureCapacity()` and `capturePrintedName()`. A known capacity or name wins over a `placeholder` option; set `captured` to change what prints after capture.

```text
{{#each parties.landlord.signatories}}
{{signature("final-sig")}}
{{printedName("landlord-name")}}, {{capacity("landlord-cap")}}
{{/each}}
```

Before signing, for a signatory `Dana Lee` who signs as `Managing Member`, this prints `[SIGNATURE]`, then `Dana Lee, Managing Member`. For a signatory with no name or capacity, it prints `[PRINTED NAME], [CAPACITY]`.

A template error rejects the layer at validation and render when a directive's location is not a string, or when it is used outside a party or signatories loop with no party given. A party argument that is not a party prints `[<Name> error: no party. Use it inside a party or signatories loop, or pass the party first.]`.

## Related

* [Renderers](/sdk/renderers) - Renderer interface overview
* [Formatting](/sdk/formatting) - Value formatting
* [Signature](/schemas/primitives/signature) - Signature data type
