See all tutorials

Tutorials » Invoice example, a PDF file with dynamic content

In most of the web applications the generated PDF files contain some dynamic content, like a username, account balance, a list of transactions, a list of shopping items and so on. This tutorial will generate a PDF with dynamic text content, next tutorials will include other type of dynamic content (like dynamic images) in the generated PDF file.

The tutorial will continue one of the previous tutorials: How to include a static image in a PDF file.

To generate an invoice example with dynamic text in a PDF file created with Webpagebytes CMS take the following steps

Update the controller to include dynamic text

Step 1

The dynamic text content from the PDF file will be populated in the controller part and will be added in the model.
In the controller code example provided below, the bold part will add the dynamic random generated values to the model.

package com.example;

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.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.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);
    }

    @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");
        
        // 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));        
        
        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);
        }          
    }
}

Combine the XLS-FO content with Freemarker script to display dynamic text

Step 2

The XLS-FO site page was changed to simulate a sales invoice example. It contains static and dynamic parts, the dynamic text parts are inserted with Freemarker script, the values are taken from the CMS application model.

The bold text fragments represent the Freemarker scripts that insert the dynamic text content, the values are the ones populated in the controller at Step 1.

<?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> Date:  ${wpbAppModel["today"]?date}  </fo:block>
                        <fo:block> Invoice#:  ${wpbAppModel["invoice_no"]?string.computer}  </fo:block>
            </fo:block-container>
        </fo:block>
        <fo:block>
            <fo:block-container position="absolute" width="80mm" left="15mm" 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="80mm"  left="105mm" 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, which will change on each document load.
Dynamic PDF generated with Webpagebytes CMS - Sales invoice example

Fork me on GitHub