DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Trending

  • Docker Model Runner: Streamlining AI Deployment for Developers
  • Secure by Design: Modernizing Authentication With Centralized Access and Adaptive Signals
  • Accelerating Debugging in Integration Testing: An Efficient Search-Based Workflow for Impact Localization
  • Build Your First AI Model in Python: A Beginner's Guide (1 of 3)

Document Generation With Dynamic Image Generation

Using dynamic images in a document generation process to make the dynamic even more dynamic.

By 
Raymond Camden user avatar
Raymond Camden
·
Updated Apr. 10, 22 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
11.3K Views

Join the DZone community and get the full member experience.

Join For Free

One of the fascinating aspects of Adobe Document Generation is how incredibly flexible it is. One aspect of the API that can enhance the final result is the ability to include images in your template. In a typical use case, you would provide a static image defined in your data used with the API. In this blog post, I will demonstrate a more advanced example — dynamically generating images, in our case, charts, on the fly.

The Basics

Before we get into a more advanced demo, let’s quickly cover the basics. (My coworker has an intense look into Document Generation and images you should check out.) As our docs describe, using a dynamic image in your Word template requires a few steps.

First, you add an image to your document. It doesn’t matter what image you pick, it’s just a placeholder, but you’ll want to place it in your document as you like and ensure you’ve sized it as expected. Once done, you right-click on the image and select the “Edit Alt Text” option. In that alt text, you supply JSON:

JSON
 




xxxxxxxxxx
1


 
1
{
2
  "location-path": "logo",
3
  "image-props": {
4
    "alt-text": "This is an alt-text for the image placeholder"
5
  }
6
}


The location-path property must point to the key-value used in your data that includes the image data. So, for example, given the above location-path value, the data I use with the API could look like so:

JSON
 




xxxxxxxxxx
1


 
1
{
2
    "name":"Some Random Name",
3
    "age": 48, 
4
    "logo": "<base64 encoded image>"
5
}



As the example demonstrates, the image data must be a Base64 encoded version of the image. If you’ve never seen that before, it looks somewhat like this:

"data:image/png;base64,aVeryLongListofCharacters"

You can also use the Word Add On to insert images for you. If the sample data you added includes a Base64 value, you can select it in the Images portion of the Advanced tab.

Add images in Advanced tab

So, you can already dynamically change the image in the final result PDF or Word document at this point. To do so, you swap out the value. Imagine you’ve got two options for an image in your document, a picture of a cat or a dog. You embed a placeholder image in the Word template and link it to a value, pet. Before sending your template and data to the Document Generation API, you would use the correct value:

JavaScript
 




xxxxxxxxxx
1


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



As you can see, depending on some particular boolean value, the data will either have the encoded version of the cat or dog picture. (Obviously, one is better than the other, and I’m talking about the cat.)

While this qualifies as dynamic, we can take it a step further.

Using Dynamic Images

We will create a document describing the number of cats in a shelter over the previous six months for our scenario. This data is returned from an internal reporting system and can be represented like so:

JSON
 




xxxxxxxxxx
1
10


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


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

Template with table of data

By itself, it’s nice and straightforward and outputs cleanly. Here’s what the PDF looks like when generated:

Generated PDF

It “works,” but a chart could make it easier to read. You could more clearly see trends over time and make better judgments based on the data provided. But how do we get a dynamic chart into the Word template?

First, we need to find a service that can create the chart while also, which is the crucial part, giving us access to the raw image data of the chart. There are a thousand charting services out there, specifically for web developers. However, many of these charting libraries will render their library in a browser environment and when the JavaScript of a particular web page is viewed. We need a service that creates an actual image that could be requested via our server-side code and translated into Base64.

For our demo we are going to make use of QuickChart. QuickChart is a “service wrapper” around the open source Chart.js package. It basically takes the functionality of Chart.js and lets you get static images of charts by crafting 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 a URL parameter defining various aspects of the chart, including the type (bar), the labels, and the actual data. You can see the result of that here:

Users and Revenue table

While the URL is a bit complex (and can be even more complex), it provides a solution to our issue. Given that we have our data from our internal AP, we must “rewrite” it in a URL that works for QuickChart.

I built that first. It takes in my ordered data to create a URL on QuickChart that uses the line graph format and specifies a specific height and width. Here’s that function:

JavaScript
 




xxxxxxxxxx
1


 
1
function generateQuickChartURL(arr) {
2
    let labels = arr.map(d => d.date);
3
    let data = arr.map(d => d.amount);
4
    
5
    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`;
6
    return url;    
7
}



Perhaps I would modify it here if I wanted to add more chart features, like custom colors. With that done, I added a placeholder image to my Word doc and specified the size. Ben covers this as tip #6 in his excellent article, Adobe Document Generation API: Working with Images.

One thing I would add to this recommendation is to switch Word to use pixel height and width for images instead of inches. In your Word settings, under Advanced, go to Display and enable “Show pixels for HTML features”:

Enable “Show pixels for HTML features”

We can set a specific height and width (500 by 300) for the image and center it right beneath the table with this enabled.

Set a specific height and width

The alt text for the image looks like so:

JSON
 




xxxxxxxxxx
1


 
1
{
2
    "location-path": "image"
3
}



As a reminder, when we pass our data to the Document Generation API, it will expect the image key to containing the Base64 data of our image. How do we do that? With another function!

JavaScript
 




xxxxxxxxxx
1


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



The urlToBase64 function does precisely what it sounds like - it hits a remote URL, gets the data, and converts it. Now we have all the parts we need, let's look at a complete example:

JavaScript
 




xxxxxxxxxx
1
92


 
1
const PDFToolsSdk = require('@adobe/documentservices-pdftools-node-sdk');
2
const fs = require('fs');
3
const fetch = require('node-fetch');
4

          
5
(async () => {
6

          
7
    let input = './catreport.docx';
8
    let data = JSON.parse(fs.readFileSync('./cats.json'));
9
    let output = './catreport.pdf';
10

          
11
    if(fs.existsSync(output)) fs.unlinkSync(output);
12

          
13
    let url = generateQuickChartURL(data.numberOfCats);
14
    // get my image 
15
    data.image = await urlToBase64(url);
16

          
17
    await generateFromTemplate(input, data, output, './pdftools-api-credentials.json');
18

          
19
})();
20

          
21
/*
22
I'm specifically designed to return a url for a line item chart based on my cat array 
23
- must include 'date' and 'amount'
24
*/
25
function generateQuickChartURL(arr) {
26
    let labels = arr.map(d => d.date);
27
    let data = arr.map(d => d.amount);
28
    
29
    
30
    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`;
31
    return url;    
32
}
33

          
34
async function urlToBase64(url) {
35
    let resp = await fetch(url);
36
    let header = resp.headers.get('content-type');
37
    let body = await resp.arrayBuffer();
38
    
39
    data = 'data:' + resp.headers.get('content-type') + ';base64,' + Buffer.from(body).toString('base64');
40
    return data;
41
}
42

          
43
async function generateFromTemplate(template, data, dest, creds) {
44
    return new Promise((resolve, reject) => {
45

          
46
        // Initial setup, create credentials instance.
47
        const credentials =  PDFToolsSdk.Credentials
48
        .serviceAccountCredentialsBuilder()
49
        .fromFile(creds)
50
        .build();
51

          
52
        // Create an ExecutionContext using credentials.
53
        const executionContext = PDFToolsSdk.ExecutionContext.create(credentials);
54

          
55
        const documentMerge = PDFToolsSdk.DocumentMerge,
56
        documentMergeOptions = documentMerge.options;
57

          
58
        //dest determines if Word or PDF
59
        let format;
60
        let destExt = dest.split('.').pop().toLowerCase();
61
        if(destExt === 'docx') format = documentMergeOptions.OutputFormat.DOCX;
62
        else if(destExt === 'pdf') format = documentMergeOptions.OutputFormat.PDF;
63
        else throw('Invalid destination extension')
64

          
65
        // Create a new DocumentMerge options instance.
66
        options = new documentMergeOptions.DocumentMergeOptions(data, format);
67

          
68
        // Create a new operation instance using the options instance.
69
        const documentMergeOperation = documentMerge.Operation.createNew(options);
70

          
71
        // Set operation input document template from a source file.
72
        const input = PDFToolsSdk.FileRef.createFromLocalFile(template);
73
        documentMergeOperation.setInput(input);
74

          
75
        // Execute the operation and Save the result to the specified location.
76
        documentMergeOperation.execute(executionContext)
77
        .then(result => result.saveAsFile(dest))
78
        .then(() => resolve(true))
79
        .catch(err => {
80
            if(err instanceof PDFToolsSdk.Error.ServiceApiError
81
                || err instanceof PDFToolsSdk.Error.ServiceUsageError) {
82
                console.log('Exception encountered while executing operation', err);
83
                reject(err);
84
            } else {
85
                console.log('Exception encountered while executing operation', err);
86
                reject(err);
87
            }
88
        });
89

          
90
    });
91

          
92
}



Taking it from the top, I begin by specifying variables for my input, data, and output. My cat data, in this case, is a complex coded JSON file, as shown above. I then call generateQuickChatURL with my data and assign the result to the image value. Finally, this gets passed to a utility function (generateFromTemplate) that uses our SDK to create the PDF. Here’s how the final PDF looks:

Final PDF


Published at DZone with permission of Raymond Camden, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!