ColdFusion 8: Working with PDFs (Part 5)

Today's PDF entry is all about merging. ColdFusion 8 allows us to merge any number of PDFs, whether from files or directly in memory. What are some usage examples? Your site could have a standard disclaimer that you want added to the front of each PDF you create. You may have a standard credits page you want to add to the end. Whatever the need - ColdFusion makes it pretty simple, so let's take a look.

As I mentioned above, you can work with PDFs on the file system or with PDFs in memory. Let's first take a look at PDFs on the file system. The CFPDF tag takes a directory attribute. This directory consists of the PDF files you want to merge. By default ColdFusion will merge all files in the folder. There are three things to consider when working with a folder:

  1. CFPDF will sort your PDFs by timestamp first. You can supply the order attribute to change this to name.
  2. CFPDF will sort your PDFs in reverse order. So if you use name, PDFs will be sorted Z to A. You can change this by using the ascending attribute. The default value is no.
  3. CFPDF will try to merge every file in a folder. If a folder contains non-PDF files, ColdFusion will ignore it. If you do not want ColdFusion to ignore non-PDF files, use stopOnError=true.

So let's look at a simple example:

<cfdocument name="pdf1" format="pdf">
<cfoutput>
This is PDF 1 at #timeFormat(now())#
</cfoutput>
</cfdocument>

<cfdocument name="pdf2" format="pdf">
<cfoutput>
This is PDF 2 at #timeFormat(now())#
</cfoutput>
</cfdocument>

<cfset savedFolder = expandPath("./pdfs")>

<cffile action="write" file="#savedFolder#/pdf1.pdf" output="#pdf1#">
<cffile action="write" file="#savedFolder#/pdf2.pdf" output="#pdf2#">

<cfpdf action="merge" directory="#savedFolder#" name="mergedpdf">

<cfcontent type="application/pdf" reset="true" variable="#toBinary(mergedpdf)#">

The code begins by simply creating two PDFs. These PDFs are stored to the file system in a subfolder named pdfs. The important line is here:

<cfpdf action="merge" directory="#savedFolder#" name="mergedpdf">

I simply specify a directory and in my case, a name variable to store the result in memory. Lastly I serve up the PDF with the cfcontent tag. If you run this you will notice that the PDF seems backwards. PDF2 is on page 1, and PDF1 is on page 2. This makes sense if you remember the above notes. The default order is by time, descending, and PDF2 was written out first.

Now let's take it up a notch and introduce a new tag, cfpdfparam. The cfpdfparam tag is only used with merging PDFs. It lets you do all kinds of fun things. It gives you the power to provide more control over the order. It lets you specify a page range for each PDF. (So for example, merge pages 1-10 in pdf 1, pages 13-19 in pdf 2, and pages 90-100 in pdf 3.) You can also supply passwords for individual PDFs that need them. Pretty cool, eh? Here is a simple example:

<cfdocument name="pdf1" format="pdf">
<cfoutput>
This is PDF 1 at #timeFormat(now())#
</cfoutput>
</cfdocument>

<cfdocument name="pdf2" format="pdf">
<cfoutput>
This is PDF 2 at #timeFormat(now())#
</cfoutput>
</cfdocument>

<cfpdf action="merge" name="mergedpdf">
   <cfpdfparam source="pdf1">
   <cfpdfparam source="pdf2">
</cfpdf>

<cfcontent type="application/pdf" reset="true" variable="#toBinary(mergedpdf)#">

This example is much like the first one. I create two PDFs with cfdocument. This time though I don't bother saving them to the file system. I then do the merge operation, but note the use of cfpdfparam. Now my order will work correctly because I explicitly specified the proper order. I could have used filenames as well. (And let me thank Adobe again for supporting relative paths!)

One final note - another option for merging PDFs is "keepBookmark". This tells CFPDF to keep the bookmarks in the source PDF files. I'll be talking about bookmarks more in the next entry.

Please let me know if you are enjoying this series. The last entry didn't get any comments so I want to make sure folks are still getting it. :)

Comments

While I may not be using the CFPDF tag anywhere, it's definitely useful to just read through your posts. There's usually something in there that will come in handy (even if I don't know it yet) :)
# Posted By Gareth | 7/17/07 10:07 PM
Ray,
This is definatly useful. I have to admit, back in 2000 I was working with technology XSL:FO to create PDF. I took about a day of work to create a single PDF and lay it out pixel-perfect. It was major pain in the... Since then, I'm kinda scared of generating PDF and after reading your series, I'm starting to reconsider.

Thanks again.
# Posted By Michael Khait | 7/18/07 12:52 AM
What about getting rid of certain elements? Such as images - I may want to print out a pdf but I'd like to get rid of that image on the top of the page so as to save my precious color ink (isn't it obscene how you're forced to pay lots of money for color cartridge??).
# Posted By Lola LB | 7/18/07 5:54 AM
What about getting rid of certain elements? Such as images - I may want to print out a pdf but I'd like to get rid of that image on the top of the page so as to save my precious color ink (isn't it obscene how you're forced to pay lots of money for color cartridge??).
# Posted By Lola LB | 7/18/07 5:56 AM
What about getting rid of certain elements? Such as images - I may want to print out a pdf but I'd like to get rid of that image on the top of the page so as to save my precious color ink (isn't it obscene how you're forced to pay lots of money for color cartridge??).
# Posted By Lola LB | 7/18/07 5:56 AM
You can remove a watermark, but just removing an image - I don't believe you can do that. You could possibly "cheat" and put an image as a watermark, but just plain white with opacity set right and use it to cover it up.
# Posted By Raymond Camden | 7/18/07 7:56 AM
When i use the cfpdf tag to merge documents, the data in the form is not merged.

I previously created the documents with cfpdfform. I verified that the documents are stored with the data in the form, however when i merge the docuemtns and look at the new form the docuemtns are merged, but the data is gone.

Is there a way to merge the documents and keep the data?
# Posted By Mike | 8/22/07 9:23 AM
Not sure. I haven't done much at all with the forms yet. It was plan too - but life got busy. ;)
# Posted By Raymond Camden | 8/22/07 9:29 AM
Finally this coding (cfpdf merge 2 documents in 1) solve my problem. Now my system can generate a pdf file with combination of landscape & potrait orientation.
# Posted By fier | 10/23/07 1:03 AM
I use cfdocument to print out personnel biographies. You select a user, click print, and bam, a .pdf is generated (sometimes they are 2 or 3 pages long, depending upon the content in their sql record). What I need to do is allow a user to pick a few names, and click print and generate one pdf with multiple biographies. The problem I have is dealing with page numbering. I need the page numbering to be based on the person and not the overall file page count. (If a person's bio is two pages, I need to print "continued" on their second page. The problem I have is that EVERY person after page one is essentially not the first page, so they all get the continue message)
# Posted By BogieCat | 9/12/08 8:52 AM
The page numbering is easy. Just look at the docs for cfdocumentsection. It allows for page numbers based on the section.

Your second issue (if page 2 for person) is not something I know how to do. You cna obviously force a page break, but there is no way to say - if this is page 2, add X.

Wait! I'm wrong. The docs give a good example of this - even and odd numbering. The following text is from the cf ref.

ColdFusion 8 lets you use the scope variables inside any expression within a cfdocumentitem tag. For example, you
can use the currentpagenumber variable to place the section name on even pages and the chapter name on odd
pages in the header, as follows:
<cfdocument format="flashpaper">
<cfdocumentitem type="header">
<cfif (cfdocument.currentpagenumber mod 2) is 0>
<cfoutput>#sectionTitle#</cfoutput>
<cfelse>
<cfoutput>#chapterTitle#</cfoutput>
</cfif>
</cfdocumentitem>
...
</cfdocument>

That should give you what you need.
# Posted By Raymond Camden | 9/12/08 9:02 AM
I've used this for page numbering before. My problem exists when I try to create a .pdf with more than just one person data. How do I page number based on a person instead of the whole .pdf? First person has two pages (so they are 1 and 2) but then second person in .pdf is page 3, instead of being page one of their own record.
# Posted By BogieCat | 9/12/08 10:31 AM
Um - did you see my comment concerning documentsection? It lets you do per section page numbering. So just make each person a section. Please read the docs on the cfdocumentsection tag.
# Posted By Raymond Camden | 9/12/08 10:34 AM
Sorry.. Guess I mis understood. Not sure how to make it loop thru and see each person as a section...
# Posted By BogieCat | 9/12/08 11:58 AM
Doesn't your code already loop over people? Something like

<cfquery name="people">
#name#
</cfquery>

Just add cfdocumentsection tags in the loop.
# Posted By Raymond Camden | 9/12/08 12:10 PM
Yes, code has two queries, and lots of outputs.. With more than one query, I don't know how to get it to loop thru without messing up the cfoutputs...
# Posted By BogieCat | 9/12/08 12:32 PM