Chengzhihe: how to generate documents using dynamic images in front-end development? Share the method of generating image documents!

Posted by MSK7 on Tue, 21 Dec 2021 11:44:58 +0100

Adobe Document Generation One of the most attractive aspects is that it is very flexible. One aspect of the API that can really enhance the end result is the ability to include images in the template. In a typical use case, you will provide static images defined in the data used with the API. In this blog post, I will show a more advanced example - dynamically generating images, and in our example, dynamically generating charts.

Basic knowledge

Before we move on to a more advanced presentation, let's quickly introduce the basics. (my colleague) Very deeply After studying document generation and images, you should also check it.) As we said file As described above, using dynamic images in Word templates requires several steps.

First, you add the image to the document. It doesn't matter what image you choose, it's just a placeholder, but you need to place it in the document as needed and make sure it's resized as expected. When finished, right-click the image and select the edit alternate text option. In this alternative text, you provide JSON:

JSON:

{
  "location-path" : "logo" ,
  "image-props": {     "alt-text": "This is an alt-text for the image placeholder"
  }
}

The location path attribute must point to the key value used in the data containing the image data. For example, given the above location path value, the data I use with the API may be as follows:

JSON:

{  "name" : "Some Random Name" ,
    "age" : 48 ,
    "logo" : "<base64 encoded image>"
}

As shown in the example, the image data must be the Base64 encoded version of the image. If you've never seen it before, it looks a bit like this:

Data: image / png; base64, a very long character list

You can also insert images for you using Word Add On. If the sample data you added contains a Base64 value, you can select it in the image section of the Advanced tab.

So at this point, you can dynamically change the image in the final result PDF or Word document. To do this, you need to swap out the value. Imagine that for the image in the document, you have two options, a picture of a cat or a picture of a dog. In the Word template, you embed a placeholder image and link it to a value pet Before sending your template and data to the Document Generation API, you will use the correct values:

// data is the object you will pass to the API, it's got stuff already
if(thisPersonIsVeryCool) {
    data.pet = catBase64ImageData;
} else {
    data.pet = dogBase64ImageData;
}
// now call our API and pass the template and data

As you can see, depending on certain Boolean values, the data will have an encoded version of the cat or dog picture. Obviously, one is better than the other. Of course, I'm talking about cats

Although this is dynamic, we can go further.

Use dynamic images

For our scenario, we will create a document describing the number of cats in the shelter in the past six months. These data are returned from the internal reporting system and can be expressed as follows:

JSON:

{ { 

    "numberOfCats": [         {"date":"11/2020", "amount":210},         {"date":"12/2020", "amount":354},         {"date":"1/2021", "amount":321},         {"date":"2/2021", "amount":337},         {"date":"3/2021", "amount":298},         {"date":"4/2021", "amount":274}     ] }

The data consists of an array of values sorted from the oldest to the latest. Each item in the array has a date stamp and a numeric amount. Let's start with a template that contains a data table.

In itself, it is beautiful and simple, and the output is clean. This is what it looks like when generating a PDF:

It "works", but charts make it easier to read. You can more clearly see the trend over a period of time and make better judgments based on the data provided. But how do we get dynamic charts in Word templates?

First, we need to find a service that can create charts at the same time, which is a crucial part, so that we can access the original image data of the chart. You see, there are about a thousand chart services for web developers. However, many of these chart libraries will render their libraries in the browser environment and when viewing JavaScript for specific web pages. What we need is a service to create an actual image, which can be requested and converted to Base64 through our server-side code.

For our demonstration, we will use QuickChart . QuickChart is around open source Chart.js The service wrapper for the package. It basically uses chart JS, and allows you to obtain the static image of the chart by making a URL. For example, consider this URL:

 https://quickchart.io/chart?c={type:'bar',data:{labels:['Q1','Q2','Q3','Q4'], datasets:[{label:'Users ',data:[50,60,70,180]},{label:'Revenue',data:[100,200,300,400]}]}}

You can see the URL parameters that define all aspects of the chart, including type (bar), label, and actual data. You can see the results here:

Although the URL is a bit complex (or even more complex), it provides a solution to our problem. Given that we have data from the internal API, all we have to do is "rewrite" it in the URL for QuickChart.

I built that first. It takes my ordered data and uses it to create a URL on QuickChart that uses a line chart format and specifies a specific height and width. This is the function:

 

function generateQuickChartURL(arr) {     let labels = arr.map(d => d.date);     let data = arr.map(d => d.amount);        let url = `https://quickchart.io/chart?c={type:'line',data:{labels:${JSON.stringify(labels)},datasets:[{label:'Cats',data:${JSON.stringify(data)}}]}}&width=500&height=300`;     return url;     }

If I want to add more chart functions, such as custom colors, I will modify them here. When I finished, I added a placeholder image to the Word document and specified the size. Ben in his wonderful article Adobe document generation API: working with images This is introduced as skill 6.

One thing I want to add to this suggestion is to switch Word to use pixel height and width for images instead of inches. Under advanced in Word settings, go to display and enable "display pixels for HTML function":

With this feature enabled, we can set a specific height and width (500 x 300) for the image and center it below the table.

The alternative text of the picture is as follows:

{"location-path": "image"}

As a reminder, this means that when we pass data to the document generation API, it expects the image key to contain the Base64 data of our image. What do we do? There is another function!

JSON:

async function urlToBase64(url) {
    let resp = await fetch(url);
    let header = resp.headers.get('content-type');
    let body = await resp.arrayBuffer();
    data = 'data:' + resp.headers.get('content-type') + ';base64,' + Buffer.from(body).toString('base64');
    return data;
}

The urlToBase64 function fully meets its requirements - access the remote URL, get the data, and then convert it. Now that we have all the parts we need, let's look at a complete example:

const PDFToolsSdk = require('@adobe/documentservices-pdftools-node-sdk');
const fs = require('fs');
const fetch = require('node-fetch');
(async () => {
    let input = './catreport.docx';
    let data = JSON.parse(fs.readFileSync('./cats.json'));
    let output = './catreport.pdf';
    if(fs.existsSync(output)) fs.unlinkSync(output);
    let url = generateQuickChartURL(data.numberOfCats);
    // get my image 
    data.image = await urlToBase64(url);
    await generateFromTemplate(input, data, output, './pdftools-api-credentials.json');
})();
/*I'm specifically designed to return a url for a line item chart based on my cat array - must include 'date' and 'amount'
*/
function generateQuickChartURL(arr) {
let labels = arr.map(d => d.date);
    let data = arr.map(d => d.amount);
    let url = `https://quickchart.io/chart?c={type:'line',data:{labels:${JSON.stringify(labels)},datasets:[{label:'Cats',data:${JSON.stringify(data)}}]}}&width=500&height=300`;
    return url;    
}
async function urlToBase64(url) {
    let resp = await fetch(url);
    let header = resp.headers.get('content-type');
    let body = await resp.arrayBuffer();
    data = 'data:' + resp.headers.get('content-type') + ';base64,' + Buffer.from(body).toString('base64');
    return data;
}
async function generateFromTemplate(template, data, dest, creds) {
    return new Promise((resolve, reject) => {
        // Initial setup, create credentials instance.
        const credentials =  PDFToolsSdk.Credentials
        .serviceAccountCredentialsBuilder()
        .fromFile(creds)
        .build();
        // Create an ExecutionContext using credentials.
        const executionContext = PDFToolsSdk.ExecutionContext.create(credentials);
        const documentMerge = PDFToolsSdk.DocumentMerge,
        documentMergeOptions = documentMerge.options;
        //dest determines if Word or PDF
        let format;
        let destExt = dest.split('.').pop().toLowerCase();
        if(destExt === 'docx') format = documentMergeOptions.OutputFormat.DOCX;
        else if(destExt === 'pdf') format = documentMergeOptions.OutputFormat.PDF;
        else throw('Invalid destination extension')
        // Create a new DocumentMerge options instance.
        options = new documentMergeOptions.DocumentMergeOptions(data, format);
        // Create a new operation instance using the options instance.
        const documentMergeOperation = documentMerge.Operation.createNew(options);
        // Set operation input document template from a source file.
      const input = PDFToolsSdk.FileRef.createFromLocalFile(template);
        documentMergeOperation.setInput(input);
        // Execute the operation and Save the result to the specified location.
        documentMergeOperation.execute(executionContext)
        .then(result => result.saveAsFile(dest))
        .then(() => resolve(true))
        .catch(err => {
            if(err instanceof PDFToolsSdk.Error.ServiceApiError
                || err instanceof PDFToolsSdk.Error.ServiceUsageError) {
                console.log('Exception encountered while executing operation', err);
                reject(err);
            } else {
                console.log('Exception encountered while executing operation', err);
                reject(err);
            }
        });
    });
}

Starting at the top, I first specify variables for my input, data, and output. In this case, my cat data is a hard coded JSON file, as shown above. Then I call generateQuickChatURL to my data and assign the result to the image value. Finally, this will be passed to the generateFromTemplate utility function () that uses our SDK to create PDF. Here is what the final PDF looks like:

You can find this script along with Word templates and PDF output in my GitHub Repository: https : //github.com/cfjedimaster/document-services-demos/tree/main/chart_demo

Topics: Python