textRenderer

Last updated on

Render Paradoc artifacts to text-based formats

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: 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

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:

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

API

textRenderer()

Factory function that creates a configured text renderer instance.

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

Parameters

options?: TextRendererOptions
Configuration options

TextRendererOptions

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

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:

id: "text"
Renderer identifier
Methods
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.

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

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.

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.

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

Automatic Formatting

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

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:

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, 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

{{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

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.

FormatUnsignedSigned, no imageSigned, 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.

DirectiveBefore captureAfter 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.

{{#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.].

On this page