textRendererLast updated on
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
TextRendererOptions
TextSignatureOptions
Returns
Returns an ParadocRenderer object with:
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
}): stringParameters
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[] | undefinedAutomatic 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 signssignatureDate, 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.
| 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]_ |  |
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.
{{#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 - Renderer interface overview
- Formatting - Value formatting
- Signature - Signature data type