Abstract

Force.com lets you easily generate PDFs on the fly, based on a simple attribute of a Visualforce page. You can use this functionality for many purposes: for example, you can generate professional looking invoices or quotations. Of course, you can also email, store, print or display the PDFs.

The key to professional looking PDF is styling, and you can accomplish the styling of generated PDFs by modifying the cascading style sheets (CSS) of the page. This article will provide an overview of CSS styling to improve presentation, support pagination and the ability to add page numbering to any PDF document. We will use the free Simple Quotes app, available on the Force.com AppExchange to demonstrate the techniques discussed with a common, real world example.

Inline Styles vs. External Stylesheets in CSS

Cascading Style Sheets, or CSS as they are typically known, provide the ability to embed styling instructions inline within a particular tag, such as a standard HTML div tag. Visualforce tags, such as apex:outputPanel, also support this - passing on the styling to the generated HTML. Before we jump into creating PDFs, let's look at how to attach styling to Visualforce pages.

The snippet below demonstrates a simple example of adding an inline style to change the output panel's background color using a hexadecimal value:

<apex:outputPanel layout="block" style="background-color:#0000FF"/>

While inline styles are useful, they can quickly become unmanageable: If, at a later date you decide that all outputPanels should be dark purple rather than light blue, you would need to find and replace each instance where the style is set on a tag.

Thankfully we can avoid this problem by centralising CSS styles into a file called an external stylesheet. This technique is used extensively to control fonts, colors, backgrounds, and much more. It is a best practice that you can follow with custom Visualforce pages as well, by just adding a few lines of code.

First let's create an external stylesheet file. Open your favorite text editor, and add the following:

.standardBackground {
  background-color: #0000FF;
}

Save the file on your local drive and call it qstyles.css

What you just created is a style that can be associated to an HTML, or Visualforce component that supports style classes.

Before you can use the style in the Visualforce component, you need to upload the stylesheet as a Static Resource to your Force.com environment, and give it a name. After that, you can reference the external stylesheet via it's name in a Visualforce page via the apex:stylesheet tag.


To upload the style sheet, log on to your Developer Environment and navigate to Setup | Develop | Static Resources. Static resources that are made available in this way have other benefits, including being cached. See Delivering Static Resources with Visualforce for more details. The following image illustrates what you'll see after uploading a static resource in a ZIP file:

Adding a resource bundle with our stylesheet

We've named the resource advancedpdfresource. You can now reference the stylesheet in the Visualforce page using this code snippet, which you usually place near the top of a page:

 <apex:stylesheet value="{!URLFOR($Resource.advancedpdfresource, 'qstyles.css')}"/> 
 

Note how it uses the name of the resource advancedpdfresource, which essentially references the ZIP file, and the name of the file within the ZIP (qstyles.css).

To use the style in your Visualforce markup, insert a Visualforce tag with the styleClass attribute. Something like this:

 <apex:outputPanel layout="block" styleClass="standardBackground"/>

The result will look something like this: StyleResult.png

Rendering a Force.com page in PDF

While the previous section gave a very simple snippet of CSS in action, CSS can be a lot more complex and let you very precisely manipulate the visual aspects of your rendered pages. CSS itself is beyond the scope of this tutorial, but if you are interested in more check out W3 Schools for a fantastic resource. Later, in this tutorial we will come back to CSS to help us accomplish pagination, and page numbering, but for now there is one fundamental we need to cover before diving into a real world example.

Let's now look at how to render a Visualforce page as a PDF document. The only thing you need to do render a Visualforce page as PDF is add the following attribute to your apex:page tag: renderAs="pdf"

That's it! Any page with this attribute will render as PDF instead of HTML. The renderAs attribute is a very useful attribute in Visualforce development as with very little work you can render a page as PDF.

One best practice worth noting is that, when you are rendering a page as something different than a typical Visualforce page you typically do not want to include the standard header. This can be easily removed by adding the following attribute to your apex:page tag: showHeader="false"

Here's a simple Visualforce page that you can create which does the job:

<apex:page renderAs="pdf" showHeader="false">
  <apex:stylesheet value="{!URLFOR($Resource.advancedpdfresource, 'qstyles.css')}"/> 
  <apex:outputPanel layout="block" styleClass="standardBackground"/>
</apex:page>

Time for a Real World Example

I like to use real world examples in my tutorials to demonstrate how to apply the learnings in real situations. For this tutorial I am going to base the real world example on one of the primary uses of PDF generation I come across when talking to salesforce.com customers: printing PDF quotations. Quotations is a great example of a customer document that is functional, but also represents a company to all of its clients. As a result, quotations have to look good, and solve a business need.

To make the example even more 'real world' we are going to leverage an existing Force.com AppExchange app called Simple Quotes. Simple Quotes provides the necessary Apex classes, and Visualforce pages to generate PDF quotations. Once the app is installed you will have a new Custom Object available in your org, called "Quotes". (If you don't see a quotes tab after install, you can create one by clicking Setup | Create Tabs, and clicking the New button to the right of the Custom Object Tab heading.)

Here's an example of a quote record:

Our new Quotes tab

It is worthwhile taking a few minutes to become familiar with the functions of the Simple Quote application by creating a new Quote, a series of Line Items (create 10-20 line items; we will use them later for our pagination example), and finally creating a PDF quotation by clicking on the Generate Quote PDF custom link. This final step, generates the PDF file, and adds it to the Notes & Attachments related list. The first thing you will notice is that the PDF quotes created need a little bit of work to make them look a little more professional.

Once you have created a few quotes, and feel comfortable with the process let's continue.

Generate a quote

For this tutorial, we are going to dive into two specific components.

  • SalesQuotes.cls, a Controller extension class responsible for retrieve quote line items, calling the PDF page, and attaching the generated PDF to the quote. We will be updating this class to prepare the quote line items for pagination.
  • quotePDF.page, a Visualforce page where the all the fields required to appear in the PDF quotation are laid out. We will be creating a new and improved version of this page to support pagination, and page numbering.

Because this is a real world example we are not going to gloss over the details of some of the "heavy lifting" behind the scenes to prepare our quote for pagination, for example. The good news is once we have made these code changes, we can jump back into CSS to complete the process.

The Heavy Lifting - Modifying the Controller to support Pagination

Our goal here is to modify the Apex code in the controller in order to support pagination. Let's jump right in!

Variable Declaration

In order to paginate correctly, we need to prepare the data that is going to be displayed. Our first step of enabling pagination is to modify the Controller extension that comes with Simple Quotes. Create a class level variable (a List of quote line item arrays) to hold a series of quote line items to be printed on each page. We will also add two static Integers to control how many rows per page we want to display:

 //controls how many quote line items are displayed on page 1
 private static Integer FIRST_BREAK = 10;
 //controls how many quote line items are displayed on subsequent pages
 private static Integer SUBSEQ_BREAKS = 20;
 
 public List<SFDC_520_QuoteLine__c[]> pageBrokenQuoteLines {get; private set; }

Two Integers are defined: FIRST_BREAK and SUBSEQ_BREAK. The reason for this is that the first page of a quotation typically include company details, address details, and other information. The result is that you can display fewer line item rows on the first page than on subsequent pages. The numbers I have used here are representative only. For your implementation they will likely change based on layout, and style of your PDF.cool

Preparing the Quote Line Items

With the variables defined, it is time to do some more heavy lifting, and populate the pageBrokenQuoteLines List defined in the Controller above. Let's create a new method, prepareQuoteLinesForPrinting() to encapsulate the logic:

 //splits the quote lines into an approximate number of rows that can be 
 //displayed per page
 private void prepareQuoteLinesForPrinting()
 {
 	pageBrokenQuoteLines = new List<SFDC_520_QuoteLine__c[]>();
 	
 	SFDC_520_QuoteLine__c[] pageOfQuotes = new SFDC_520_QuoteLine__c[]{};
 	Integer counter = 0;
 	
 	boolean firstBreakFound = false;
        boolean setSubSeqBreak = false;        
	Integer breakPoint = FIRST_BREAK;

 	for(SFDC_520_QuoteLine__c q : quoteLineItems)
 	{
 	  if(counter <= breakPoint)
 	  {
 	     pageOfQuotes.add(q);
 	     counter++;	
 	  }
  	  if(counter == breakPoint)
  	  {
  	     if (!firstBreakFound) 
  	     {
  	        firstBreakFound = true;
                setSubSeqBreak  = true;
  	     }
  	     counter = 0;
  	     pageBrokenQuoteLines.add(pageOfQuotes);
  	     pageOfQuotes.clear();
  	  }
 	  if(setSubSeqBreak) 
          {
             breakPoint = SUBSEQ_BREAKS;
             setSubSeqBreak = false;
          }
 	}
 	//if we have finished looping and have some quotes left let's assign them
 	if(!pageOfQuotes.isEmpty())
           pageBrokenQuoteLines.add(pageOfQuotes);
 }

Phew, that is a lot of code—and you thought you were just going to learn about CSS. I told you this was a real world example! Thankfully the code is about as complex as it needs to be. We are almost done, so bare with me a little longer, before we can get to see the fruits of our labor.

Looking at the code you will see it iterating through the quoteLineItems, the original array of all line items retrieved in the queryQuoteLines method. This time however, we are going to use our variables FIRST_BREAK and SUBSEQ_BREAKS to split the array into smaller arrays which will fit on a page of our PDF quotation.

Now we need to make sure that we add a call to the new method from the end of the existing queryQuoteLines:

// constructor, loads the quote and any opportunity lines
void queryQuoteLines(id id) { 
	quote = [  Select s.Opportunity__r.Pricebook2Id, Quote_Amount_rollup__c,
			   (Select Unit_Price__c, Unit_Net_Price__c, ServiceDate__c, 
			   Sales_Discount__c, Quote__c, Qty_Ordered__c, Product2__c, 
			   Product2__r.Name, Name, Id, Ext_Price__c, 
			   Ext_Net_Price__c, Ext_Price_tmp__c, Description__c  From Quote_Lines__r
			   order by name ), 
 		s.Opportunity__r.HasOpportunityLineItem, s.Opportunity__r.Name, s.Name,
 		s.Opportunity__r.Id, s.Opportunity__c  From SFDC_520_Quote__c s 
 		where s.id = :id limit 1]; 
	quoteLineItems = quote.Quote_Lines__r;  
	for ( SFDC_520_QuoteLine__c ql : quoteLineItems ) { 
		ql.Ext_Price_tmp__c = ql.Ext_Net_Price__c;
		if ( ql.Sales_Discount__c == null ) ql.Sales_Discount__c = 0.0;
	}
	
	prepareQuoteLinesForPrinting();
}

Finally, we need to change the Visualforce page to our, yet to be created, quotePDFAdvanced.page. Search for the following line in the attachQuote method:

PageReference pdfPage = new PageReference( '/apex/quotePDF' );

And replace it with this:

PageReference pdfPage = new PageReference( '/apex/quotePDFAdvanced' );
 

That's it for our changes in the controller. We added a few global variables to manage the number of rows per page, included a new method for splitting the quote line items into arrays based on the variables, added a call to the new method from the constructor, and finally changed the page reference to our new advanced PDF quote page.

Now it is time to look at how we can render the quotation to make it look professional by using CSS.

Adding Some Style

We are going to start by defining a few more simple styles in the external stylesheet we created earlier. Open the qstyles.css file in your favorite editor and add the following two style definitions:

.infobox {
  border: 1px dashed #CCCCCC;
  font-size: 100%;
}

.quoteinfo {
  border: 2px groove #000000;
  font-size: 100%;
 
}
 

Again, you will note that we are prefixing our style with a '.' This standard CSS notation lets us define a style class, which can be associated with any HTML component or Visualforce tag that supports the style attribute. I like to take advantage of the font-size attribute within CSS to let me tweak the data in the quotation to fit the page size. This is particularly helpful when a quote line item has a lot of rows to be displayed.

For now, we are going to use the two styles above to make our quotation header details a little bit more stylish. We will add a few more soon but before we can get started, we need to set up our template.

The Simple Quotes app comes with a basic quotation Visualforce template page, quotePDF. Because we are doing some heavy customisation of the existing quotePDF, it's better to start with a clean slate, and only copy over the code needed. Go ahead and create a new Visualforce page called quotePDFAdvanced, and add the following code:

<apex:page standardController="SFDC_520_Quote__c" showHeader="false" renderAs="pdf" extensions="salesQuotes" > 
	
	 <apex:stylesheet value="{!URLFOR($Resource.advancedpdfresource, 'qstyles.css')}"/> 
    
    <table width=100%>
    	<tr width="100%">
 			<td width="50%" align="left">
    			<apex:image value="{!URLFOR($Resource.advancedpdfresource, 'logo.gif')}"/>
 			</td>
 			<td width=50%" align="right">
     		     <apex:panelGrid columns="1" width="50%" styleClass="quoteinfo">
     		     	<apex:outputText value="Quote#: {!SFDC_520_Quote__c.name}" />
	                <apex:outputField value="{!SFDC_520_Quote__c.lastmodifieddate}" style="text-align:right"/>
	           		<apex:outputText value="{!SFDC_520_Quote__c.opportunity__r.account.name}" />
	            	<apex:outputText value="" />
	            </apex:panelGrid>
            </td>
           </tr>
           <tr>
	           <td width="50%" align="left">
		           <apex:panelGrid columns="1" styleClass="infobox">
			     		<b>Prepared For:</b> <apex:outputText value="{!contact.name}" styleClass="contactName"/>
	                	<apex:outputText value="{!SFDC_520_Quote__c.opportunity__r.account.name}"/>
		            	<apex:outputText value="{!contact.mailingStreet}"/>
		                <apex:outputPanel ><apex:outputText value="{!contact.mailingCity}"/>, <apex:outputText value="{!contact.mailingState}"/>, <apex:outputText value=" {!contact.mailingPostalCode}"/></apex:outputPanel>
		  	            <apex:outputText value="Phone: {!contact.phone}"/>
	        		</apex:panelGrid>
    			</td>
    		</tr>
    	</table>
   <br/>
</apex:page>

Note that this is not the complete markup for the page - we'll add a few more lines later. There is a lot of going here, but for this tutorial we'll just focus on the few lines where we have applied the CSS. Note:

  • how the page uses the renderAs attribute.
  • the inclusion of the stylesheet we created (line 3)
  • references an image, also stored in the resource bundle
  • references the new styles using the styleClass attribute on the apex:panelGrid tags.

Go ahead, and save everything, making sure your syntax is valid and that you have remembered to upload your stylesheets as a static resource; Then generate your new page. You should see something similar to the screenshot below:

Our new quote header is looking good

CSS Pagination

Alright, we are fired up and our PDF quote is starting to look good. Now it is time to tackle one of the most common requests I get when working with customers on their PDF requirements: pagination. The good news is that we have already done all the hard work in our Apex Controller. Now it is time to let Visualforce, and a little CSS magic do the rest.

The secret sauce in our ability to control page breaks within CSS is a little known CSS tag called page-break-after. This tag allows you to control when a page break occurs in a HTML document, or in our case, a PDF document. You can specific whether you want the break to occur before, or after a tag the style is associated with. For this tutorial we are going to want to break after, and so we'll need some CSS that looks like this:

page-break-after:always

Now remember when we were deep in Apex code building our collection of arrays of quote line items? It is time we started putting that hard work to the test, and combine it with our page-break-after style. Start by looping over the collection or arrays:

 <apex:repeat value="{!pageBrokenQuoteLines}" var="aPageOfQuotes" id="theList">

Next we are going to add a div with the following style:

 <div style="page-break-after:always;"> 

Notice I haven't closed the div yet. Remember, our page-break-after style tells the rendering engine to insert a page break after the element the style is associated with. Everything we put inside the div will stay on the same page (unless of course the content is too long and it naturally goes over a page --- but we already thought of that with our static variables FIRST_BREAK and SUBSEQ_BREAK).

These two lines are what enables us to do pagination in our page. Firstly, we use an apex:repeat tag to iterate over the list of quote line items we prepared earlier (remember the ones, which were divided into the correct number of rows to be displayed per page). Anything within the body of the apex:repeat tag will be repeated for each element of the list.

Time to loop through our inner array of page-sized quote items we assigned to the variable aPageOfQuotes in our repeat tag. We are going to use the apex:dataTable to do this:

 <!--  Main table with quote items -->
 <apex:dataTable value="{!aPageOfQuotes}" var="c" id="theTable" headerClass="tablehead" rowClasses="odd,even" style="font-size: 50%; " columnsWidth="50px, 100px, 150px, 100px, 100px, 50px, 100px, 100px, 100px, 100px">
               <apex:column style="border: 1px"> 
                   <apex:facet name="header">Quote Line Item</apex:facet>
                   <apex:outputText value="{!c.Name}"/>
               </apex:column>
               <apex:column >
                   <apex:facet name="header" >Product Name</apex:facet>
                   <apex:outputText value="{!c.Product2__r.Name}"/>
               </apex:column>
               <apex:column >
                   <apex:facet name="header">Description</apex:facet>
                   <apex:outputText value="{!c.Description__c}"/>
               </apex:column>   
               <apex:column >
                   <apex:facet name="header">Quantity Ordered</apex:facet>
                   <apex:outputField value="{!c.Qty_Ordered__c}"/>
               </apex:column>
        		<apex:column > 
                   <apex:facet name="header">Unit Price</apex:facet>
                   <apex:outputField value="{!c.Unit_Price__c}"/>
               </apex:column>
                <apex:column styleClass="highlightcol">
                   <apex:facet name="header" >Sales Discount</apex:facet>
                   <apex:outputField value="{!c.Sales_Discount__c}"/>
               </apex:column>
               <apex:column style="text-align:center;">
                   <apex:facet name="header" >Unit Net</apex:facet>
                   <apex:outputField value="{!c.Ext_Net_Price__c}"/>
               </apex:column>
               <apex:column style="text-align:center;">
                   <apex:facet name="header" >Ext Net</apex:facet>
                   <apex:outputField value="{!c.Ext_Price__c}"/>
               </apex:column>
   </apex:dataTable>
 

You will notice we are once again going to take advantage of our new found CSS wizardry, and add a couple more style classes to jazz the table up a bit. Add the styles below to your external stylesheet, package up, and upload again:

 .tablehead {
     border: 1px solid;
     background-color: #99C68E;
     font-weight: bold;
     font-size: 120%;
 }
 
 .totals {
     background-color: #0099FF;
     font-weight: bold;
 }
 
 .infohead {
   font-size: 70%;
   font-weight: bold;
 }
 
 .odd {
  border-bottom: 1px solid #000;
  background-color: #ECE5B6;
  padding: 1px 1px 2px 2px;
  font-size: 100%;
 }
 
 .even {
    border-bottom: 1px solid #000;
    padding: 1px 1px 2px 2px;
    font-size: 100%;
 }
 
 .highlightcol {
 	background-color: #FBB917;
 }

You should be getting a little bit more familiar with the syntax for CSS now. Note how the above classes lets us add a few colors, and styles to the quote line items header information. In addition, the apex:dataTable component supports a really great attribute called rowClasses that allow you to pass in a few different style classes to iterate over odd, and even rows - we add classes with these names to support this. I have also added another style called highlightcol, which we use to highlight the Sales Discount column, just to make sure our customer sees what a great deal they are getting. Here's the result:

Now we have some style!

It is time to break our page by closing the div tag, and finally the apex:repeat tag:

		
 </div> 
 </apex:repeat>

Looking good! All we need to do now is add our totals on the last page of the PDF quotation. Here we throw in a bit of style from our stylesheet we created earlier.

	<br>
        <apex:panelGrid columns="2" width="100%" styleClass="infobox">
        	<apex:outputText styleClass="infohead" value="Quotation Totals:" />
        	<apex:outputField value="{!SFDC_520_Quote__c.Quote_Amount_rollup__c}"/>
        </apex:panelGrid>      
         
	</apex:page>

That's it. You have created a very professional PDF quotation document with pagination support. You could easily modify the example to print header and footer information per page, or add what ever you need. Go back to your Quote page, and click "Generate a PDF" to see your masterpiece in action.

A Little More CSS Magic

Just like any good tutorial, we saved a little magic for the end. Now that we have our PDF quotation that supports page breaks, we can easily produce a quote many pages long. What happens if I want to include page numbers, or perhaps have a lot of rows in my datatable that will only fit if the page orientation is set to landscape?

Thanks to a little more CSS styling, both requirements are solved with a few additions to our external stylesheet. Add the following to the external stylesheet, upload the resource, and generate your completed document.

 @page {
 /* Landscape orientation */
 size:landscape;

 /* Put page numbers in the top right corner of each
	page in the pdf document. */
 @bottom-right {
 content: "Page " counter(page) " of " counter(pages);
 }

Generating a quote now, gives us our final, eye-catching, and professional PDF quote.

Summary

This tutorial walks you through the creation of an external stylesheet, the addition of style classes, their use within Visualforce components, and introduces some little known CSS tips such as pagination, page orientation, and support for headers and footers.

We took a common requirement of PDF quote generation, and put this knowledge to the test with a complex, real-world application: Simple Quotes. The result of the modification to the Simple Quotes application is a very professional looking quotation, which organisations can send to their customers with confidence.

This has only scratched the surface of what can by done with a good working knowledge of CSS and the Force.com platform. However, the techniques introduced within this tutorial can be utilised in many different contexts where requirements may call for advanced formatting. Now it is time for you to put your knowledge to work!

References

About the author

Quinton Wall is a published fantasy author, and Developer Evangelist with Salesforce.com. He is a regular contributor to the developer blogs, and self-confessed iPhone addict. When he is not working with the Force.com platform, or writing books, you can find him on the web.