See all tutorials

Invoice example, a dynamic QR code image in a PDF file

Most of the e-commerce website will need to generate dynamic images in PDF files, these images can be QR codes, bar codes, charts and so on.

qr embedded in pdf

The tutorial will continue one of the previous tutorials: invoice example PDF generation, where a sales invoice example was presented. Here, a dynamic QR image will be embedded in the PDF file, representing the url http://www.webpagebytes.com/invoice-example/[invoice#]. Each invoice will have a different id and so a different QR code image will be embedded in each PDF.

To generate dynamic QR image in a PDF file created with Webpagebytes CMS take the following steps

Update the controller to generate QR image data

Step 1

The QR code image will be generated using zxing java library.
The image will be converted to base64 and added to the application model under key 'invoiceQR'.

The code in bold below represent the changes related to QR code generation

package com.example;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.webpagebytes.cms.WPBContentProvider;
import com.webpagebytes.cms.WPBForward;
import com.webpagebytes.cms.WPBModel;
import com.webpagebytes.cms.WPBRequestHandler;
import com.webpagebytes.cms.exception.WPBException;

public class SimplePDFController implements WPBRequestHandler {

    WPBContentProvider contentProvider;
    
    private FopFactory fopFactory;
    private TransformerFactory transformerFactory;

    @Override
    public void initialize(WPBContentProvider contentProvider) {
        this.contentProvider = contentProvider;
        
        transformerFactory = TransformerFactory.newInstance();
        fopFactory = FopFactory.newInstance();

    }

    // generates a random string of a specified length
    private String randomString(int len)
    {
        Random r = new Random();
        String result = "";
        for(int i = 0; i < len ;i++)
        {
            result = result + String.valueOf((char)(97+r.nextInt(25)));
        }
        return result;
    }

    // generates a random number of a specified number of digits
    private int randomNumber(int digits)
    {
        Random r = new Random();
        String result = "";
        for(int i = 0; i < digits ;i++)
        {
            result = result + String.valueOf((char)(49+r.nextInt(9)));
        }
        return Integer.valueOf(result);
    }
    
    private String base64QRCode(String url, int size)
    {
        Map hintMap = new HashMap();
        hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
        hintMap.put(EncodeHintType.MARGIN, 2);
        try
        {
            BitMatrix matrix = new MultiFormatWriter().encode(
                    new String(url.getBytes("UTF-8"), "UTF-8"),
                    BarcodeFormat.QR_CODE, size, size, hintMap);
            ByteArrayOutputStream bos = new ByteArrayOutputStream(4096);
            MatrixToImageWriter.writeToStream(matrix, "PNG", bos);
            return DatatypeConverter.printBase64Binary(bos.toByteArray());
        } catch (Exception e)
        {
            return "";
        }
    }
    
    
    @Override
    public void handleRequest(HttpServletRequest request,
            HttpServletResponse response, WPBModel model, WPBForward forward)
            throws WPBException 
    {
        String pageGuid = model.getCmsModel().get(WPBModel.URI_PARAMETERS_KEY).get("pageGuid");
        
        String invoiceNumber = String.valueOf(randomNumber(6));
        // populate the application specific model
        model.getCmsApplicationModel().put("today", new Date());        
        model.getCmsApplicationModel().put("invoice_no", randomNumber(6));        
        model.getCmsApplicationModel().put("name", randomString(6));
        model.getCmsApplicationModel().put("telephone", "555-" + randomNumber(4));
        model.getCmsApplicationModel().put("address", randomNumber(3) + " Park Avenue " + randomNumber(5));        
        model.getCmsApplicationModel().put("invoiceQR", base64QRCode("http://www.webpagebytes.com/invoice-example/" + invoiceNumber, 100));        
        
        ArrayList<Map<String, Object>> products = new ArrayList<Map<String,Object>>();
        Map<String, Object> product1 = new HashMap<String, Object>();
        product1.put("name", "Milk");
        product1.put("quantity", randomNumber(2));
        product1.put("price", randomNumber(3));
        products.add(product1);
        
        Map<String, Object> product2 = new HashMap<String, Object>();
        product2.put("name", "Tea");
        product2.put("quantity", randomNumber(2));
        product2.put("price", randomNumber(3));
        products.add(product2);
        
        model.getCmsApplicationModel().put("products", products);
        
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
        contentProvider.writePageContent(pageGuid, model, bos);
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        
        response.setContentType("application/pdf");
        
        OutputStream os = null;
        try
        {
            os = response.getOutputStream();         
            Transformer transformer = transformerFactory.newTransformer();
            Source source = new StreamSource(bis);
            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, os);
            Result res = new SAXResult(fop.getDefaultHandler());
            transformer.transform(source, res);
        
        } catch (Exception e)
        {
            throw new WPBException("cannot generate pdf", e);
        }     
    }
}

Update the XLS-FO content to include the image data

Step 2

The XLS-FO site page was changed to include the image from the model. The fo:external-graphic tag was used to embed the image in the generated document, specifing the base64 format and data. Some small user interface adjustments were done to fit the QR code nicely in the generated document.

The bold text embeds the QR code generated at Step 1, note the model key 'invoiceQR' used also to populate the model.

<?xml version="1.0" encoding="UTF-8"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="A4" page-width="210mm" page-height="297mm" margin="10mm">
    <fo:region-body margin-top="25mm" />
    <fo:region-before region-name="header" />
    </fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A4" initial-page-number="1">
    <fo:title> Webpagebytes CMS PDF example</fo:title>
    <fo:static-content flow-name="header">
        <fo:block>
	    <fo:block-container position="absolute" top="2mm" left="2mm" width="40mm" height="20mm">
                <fo:block>
                    <fo:external-graphic src="<@wpbImage externalKey="8c319c58-578e-4dbe-954d-b21c61e67bbe" embedded="true" />" />       
                </fo:block>                	                                         
            </fo:block-container>
            <fo:block-container position="absolute" top="1mm" left="70mm" width="120mm" height="20mm">
	        <fo:block text-align="right">   
		    <fo:basic-link  external-destination="url('http://www.webpagebytes.com')" color="blue" text-decoration="underline"> http://www.webpagebytes.com </fo:basic-link>
	         </fo:block>
	         <fo:block text-align="right">   
			    Tutorial on how to generate a PDF file with dynamic content
		  </fo:block>			
	     </fo:block-container>
        </fo:block>
    </fo:static-content>
    <fo:flow flow-name="xsl-region-body">
        <fo:block>
            <fo:block-container width="190mm" font-size="2em" border-color="#009F00" color="#009F00" border-after-style="solid" border-width="0.5mm">
                        <fo:block> Invoice example</fo:block>
            </fo:block-container>
        </fo:block>
        <fo:block>
            <fo:block-container position="absolute" width="95mm" left="0mm" top="12mm">
                        <fo:block> [COMPANY NAME] </fo:block>
                        <fo:block> [Address] </fo:block>
                        <fo:block> [Website] </fo:block>
            </fo:block-container>
            <fo:block-container position="absolute" width="95mm"  left="95mm" top="12mm" text-align="right">
                        <fo:block margin-right="2mm"> Date: ${wpbAppModel["today"]?date} </fo:block>
                        <fo:block margin-right="2mm"> Invoice#: ${wpbAppModel["invoice_no"]?string.computer} </fo:block>
                        <fo:block> 
                             <fo:external-graphic src="data:image/png;base64,${wpbAppModel["invoiceQR"]}" />    
                        </fo:block>
            </fo:block-container>
        </fo:block>
        <fo:block>
            <fo:block-container position="absolute" width="70mm" left="0mm" top="40mm">
                        <fo:block background-color="#FFF1A5" padding="2mm"> To: </fo:block>
                        <fo:block>  Name: ${wpbAppModel["name"]} </fo:block>
                        <fo:block>  Address: ${wpbAppModel["address"]} </fo:block>
                        <fo:block>  Tel: ${wpbAppModel["telephone"]} </fo:block>
            </fo:block-container>
            <fo:block-container position="absolute" width="70mm"  left="80mm" top="40mm">
                        <fo:block background-color="#FFF1A5" padding="2mm"> Ship to: </fo:block>
                        <fo:block>  Name: ${wpbAppModel["name"]} </fo:block>
                        <fo:block>  Address: ${wpbAppModel["address"]} </fo:block>
                        <fo:block>  Tel: ${wpbAppModel["telephone"]} </fo:block>            </fo:block-container>
        </fo:block>
        <fo:block>
            <fo:block-container position="absolute" width="190mm"  left="0mm" top="70mm">
                <fo:table table-layout="fixed" width="100%" border-color="#888888" border-width="medium" border-style="solid">
			<fo:table-column column-width="20mm"/>
			<fo:table-column column-width="80mm"/>
			<fo:table-column column-width="30mm"/>		
			<fo:table-column column-width="30mm"/>
			<fo:table-column column-width="30mm"/>
                        <fo:table-header background-color="#DFDFDF">
			   <fo:table-cell padding="2mm">
			       <fo:block > Item # </fo:block>
			   </fo:table-cell>
			   <fo:table-cell padding="2mm">
			       <fo:block > Description </fo:block>
			   </fo:table-cell>
			   <fo:table-cell padding="2mm">
			       <fo:block > QTY </fo:block>
			   </fo:table-cell>
			   <fo:table-cell padding="2mm">
			       <fo:block > Unit price </fo:block>
			   </fo:table-cell>		
			   <fo:table-cell padding="2mm">
			       <fo:block > Line total </fo:block>
			   </fo:table-cell>	   
			</fo:table-header>
                        <fo:table-body>
                            <#assign products=wpbAppModel["products"] />
                            <#assign total=0 />
                            <#list products as item >
                                <fo:table-row >
			           <fo:table-cell padding="2mm" >
				       <fo:block> ${item_index+1} </fo:block>
				   </fo:table-cell>
			           <fo:table-cell padding="2mm" >
				       <fo:block> ${item.name} </fo:block>
				   </fo:table-cell>
			           <fo:table-cell padding="2mm" >
				       <fo:block> ${item.quantity} </fo:block>
				   </fo:table-cell>
			           <fo:table-cell padding="2mm" >
				       <fo:block> ${item.price?string.currency} </fo:block>
				   </fo:table-cell>
			           <fo:table-cell padding="2mm" >
				       <fo:block> ${(item.price*item.quantity)?string.currency} </fo:block>
                                       <#assign total=(total + item.price*item.quantity) /> 
				   </fo:table-cell>
                              </fo:table-row>			
                            </#list>
                        </fo:table-body>
                </fo:table>  
            </fo:block-container>
        </fo:block>
        <fo:block>
            <fo:block-container position="absolute" width="50mm"  left="150mm" top="100mm">
                 <fo:block font-weight="bold"> Total: ${total?string.currency} </fo:block>
            </fo:block-container>
        </fo:block>
    </fo:flow>
</fo:page-sequence>
</fo:root>

Test the PDF generation

Step 3

Because this tutorial extended the previous tutorials, the PDF file can be obtained by accessing http://localhost:9090/pdf
Load this url several times in the browser and inspect the dynamic values and the generated QR image, these will change on each document load.
Scan the QR code with an image and it should open a page like this, Webpagebytes CMS invoice example
Dynamic QR code embedded in a generated PDF

Useful resources

Fork me on GitHub