Strucsta LogoStrucsta/Documentation

Chapter 4: Designing Pixel-Perfect PDFs

Dynamic Logic in JSONC

Target Audience: Users crafting the visual output.

If you have used Handlebars in traditional HTML or plain text templates, you are likely used to wrapping content in block tags like {{#if}} or {{#each}}.

However, because Strucsta's Render step uses JSONC (JSON with Comments) to define the pdfmake layout, dropping raw Handlebars block tags directly into the file would result in invalid JSON and break the parser.

To solve this securely, our engine parses your template into an Abstract Syntax Tree (AST) first, and then evaluates logic node-by-node. This means you use standard Handlebars {{variable}} syntax for string injection, but you use Structural Directives for logic like loops and conditionals.

1. Conditionals ($if)

Often, you only want to render a section of your PDF if a specific condition is met in your data. For example, rendering a special compliance clause only if the requiresCustomSLA boolean is true.

Instead of a Handlebars block, you use the $if directive object:

{
  "content": [
    {
      "text": "Standard Terms & Conditions",
      "style": "header"
    },
    // The engine evaluates the "$if" key. 
    // If the variable is truthy, it renders the "then" object.
    {
      "$if": "requiresCustomSLA",
      "then": {
        "text": "Custom SLA Addendum: {{customSLA_Details}}",
        "style": "legalText",
        "color": "#e53e3e"
      }
    }
  ]
}

Note: You can also include an optional "else" key alongside "then" to render fallback content.


2. Loops and Arrays ($each)

Document automation heavily relies on arrays—lists of line items, daily itineraries, or employee records. The $each directive allows you to iterate over an array from your AI reasoning step and map each item to a visual pdfmake block (like a table row or a column).

Here is how you render a dynamic list of invoice items:

{
  "content": [
    {
      "text": "Itemized Breakdown",
      "style": "h2"
    },
    {
      // The array variable to iterate over
      "$each": "lineItems",
      // The local alias for the current item in the loop
      "as": "item",
      // The layout block to duplicate for every item
      "render": {
        "columns": [
          { 
            "text": "{{item.name}}", 
            "width": "*" 
          },
          { 
            "text": "${{item.cost}}", 
            "width": 100, 
            "alignment": "right" 
          }
        ],
        "margin": [0, 5, 0, 5]
      }
    }
  ]
}

When the AST processor reaches the $each node, it dynamically expands it into a flat list of columns within the content array, safely preserving your JSON structure.


3. The 70/30 Rule: Keep Math Out of the Layout

Because pdfmake is a declarative layout engine, it does not compute math. You cannot calculate taxes, sum up totals, or manipulate string capitalization inside the JSONC template.

This is an intentional architectural choice that aligns with Strucsta's 70/30 Rule.

If your PDF requires a calculated total, that logic belongs in the Reason (AI) step. Instruct the LLM to calculate the math and output the final, formatted string into your strict JSON schema.

Incorrect Workflow: Trying to write logic in the template to multiply {{item.cost}} by {{taxRate}}.

Correct Workflow:

  1. Update your Output Schema in the Reason step to include a totalTax and grandTotal field.
  2. Instruct the AI in the prompt: "Calculate a 7% tax on the sum of all line items and output the grand total as a formatted string (e.g., '$1,070.00')."
  3. Simply map the final variable in your JSONC template: "text": "Total: {{grandTotal}}"

By keeping computation inside the Reasoning step and out of the Render step, you maintain a clean, strictly visual template that is much easier to maintain.