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
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).
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.
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.
See the Swagger-Docs - as a third party integrator, you only need to look at render-endpoint, 1. /query 2. /status and 3. /pdf
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:
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:
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 ...*/
}
}
]
{{#each this}}
and {{/each}}
{{name}}
of the object{{#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
)
@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.
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.