PrintGrid

PrintGrid is a flexible solution for dynamically creating PDFs from any source system (e.g. DAM or PIM). It enables data to be transferred directly into a print-ready PDF in a template-based format, consisting of images and text, in a customized form.

Simply put, it transforms an array of data

[
  {
    "id": "woo-album",
    "name": "Cordless combi hammer DHR400",
    "domainId": "tools"
    "metadata": {
      "battery_voltage": {
        "name": "Battery Voltage",
        "value": "24.0",
        "unit": "V",
      },
      "leerlaufdrehzahl": {
        "name": "Idle speed",
        "value": "250 - 500"
      },
      "short_description": {
        "name": "Short text",
        "value": "2x18V - SDS-MAX - 40 mm - 8,0 J"
      },
    /* ... snip ... */
  }
]

into PDF pages

PrintGrid demo output

using HTML/CSS via Handlebars and then Weasyprint (by default - this is currently a limiting factor, as they don't support the complete CSS 3 standard yet. There's an option to buy PrinceXML instead).

Architecture

PrintGrid has been designed to be source-agnostic, so in addition the the headless PrintGrid API, its accompanying Design-UI, as well as the PDF renderers, there are adapters for every flavor of data source system (called $PIM here for short).

The default use case is that the $PIM triggers the entire stack by requesting a PDF of certain article(s), receives a processing ticket in return, with which it can then poll for the completed PDF, and then finally download said PDF.

Architecture Diagram

In the back-end, the API coordinates the request, first to fetch the data from $PIM (through a configured facade), and then sending it off to a render engine, which transforms the data to HTML and then in turn to a PDF.

The intermediary HTML facilitates easy customizing of your particular layout, as many people "speak" HTML/CSS.

API

See the Swagger-Docs - as a third party integrator, you only need to look at render-endpoint, 1. /query 2. /status and 3. /pdf

Templating

We'll assume knowledge of HTML/CSS, as documenting that here as well wouldn't scale.

The templating language is Handlebars (the {{ }} bits), go read their guide for a complete picture. We'll cover the basics:

Hello World

PrintGrid was designed to print many objects at once, so you will always receive an array of data, even if you only asked for one.

Suppose you need a simple contact print:

Screenshot of a contact print

from the following data:

[
  {
    "id": "some-sku",
    "name": "Elite C:68X SLT"
    "metadata": {
      "pictures": {
        "name": "Pictures",
        "value": [
          {
            "id": "674f0a775eb4b5481aabce9a",
            "name": "48.jpg",
            "fileType": "JPG",
            "mimeType": "image/jpeg",
            "links": {
              "large": "https://some.image/large.jpg",
              "small": "https://some.image/small.jpg"
            }
          },
          {
            "id": "674f0a78cfd9f9475ca42d1d",
            "name": "22.jpg",
            "fileType": "JPG",
            "mimeType": "image/jpeg",
            "links": {
              "large": "https://some.image/large.jpg",
              "small": "https://some.image/small.jpg"
            }
          }
          /* ... snip ...*/
        ]
      }
      /* ... snip ...*/
    }
  }
]

HTML

  1. Iterate over the top-level array using {{#each this}} and {{/each}}
  2. display the {{name}} of the object
  3. finally iterate over its pictures {{#each metadata.pictures.value}} to display its {{links.small}}
<!doctype html>
<html lang="en">
 <head>
  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <base href="https://brix.printgrid.brix.ch/public/grid/">
  <link href="./style.css" rel="stylesheet" type="text/css">
 </head>
 <body>
{{#each this}}
  <h2>{{name}}</h2>
  <div id="gallery">
    {{#each metadata.pictures.value}}
    <figure>
        <img src="{{links.small}}">
        <figcaption>{{name}}</figcaption>
    </figure>
    {{/each}}
  </div>
{{/each}}
 </body>
</html>

Basically we're just shimmying down the structure of the JSON data. Note that the overall structure will always be the same, but certain metadata formats may depend on the $PIM you're using.

When copying templates around, ensure that the <base href=""> is still pointing at the correct back-end, otherwise all relative paths will break (e.g. ./style.css)

CSS

@media print {
    @page {
        size: A4; /* important for the PDF */
        margin: 5mm;

        @top-right { /* page counter */
            padding-top: 7mm;
            content: counter(page) "/" counter(pages);
            color: #005282;
        }
    }
}
html {
    font-family: 'Arial', sans-serif;
}
h2 {
    color: #005282;
    break-before: always; /* to get a new page per object */
}
#gallery {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 5mm;
}
figure {
    margin: 0;
    width: 60mm;
    height: 60mm;
    break-inside: avoid;
}
figure img {
    object-fit: contain;
    width: 60mm;
    height: 50mm;
    background-color: #f5f5f5;
}

... and that's it.

Note that your mileage may vary depending on the PDF rendering engine that is used, so something that looks good in the preview window might not work well in the PDF.

Hello Helpers

The Handlebars core is very thin on logic, basically you can only check for existence via {{#if some.field}} (truthy).

However, you may also want to test if a metadata field belongs to a certain group, which is why helpers were added.

    <h2>Technical Data</h2>
    <table>
        {{#each metadata}}
            {{#eq group "technical"}}<!-- only values from the group "technical" -->
                {{#neq @key "battery-type"}<!-- but not if the key is "battery-type" -->
                <tr>
                    <td>{{name}}</td>
                    <td>{{value}}{{#if measurement}} {{measurement.symbol}}{{/if}}</td>
                </tr>
                {{/neq}
            {{/eq}}
        {{/each}}
    </table>

... this yields a table containing only the technical data by filtering on {{#eq group "technical"}}, as seen in the first screenshot.