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. :)

Archived Comments

Comment 1 by Gareth posted on 7/18/2007 at 7:07 AM

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) :)

Comment 2 by Michael Khait posted on 7/18/2007 at 9:52 AM

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.

Comment 3 by Lola LB posted on 7/18/2007 at 2:54 PM

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??).

Comment 4 by Lola LB posted on 7/18/2007 at 2:56 PM

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??).

Comment 5 by Lola LB posted on 7/18/2007 at 2:56 PM

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??).

Comment 6 by Raymond Camden posted on 7/18/2007 at 4:56 PM

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.

Comment 7 by Mike posted on 8/22/2007 at 6:23 PM

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?

Comment 8 by Raymond Camden posted on 8/22/2007 at 6:29 PM

Not sure. I haven't done much at all with the forms yet. It was plan too - but life got busy. ;)

Comment 9 by fier posted on 10/23/2007 at 10:03 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.

Comment 10 by BogieCat posted on 9/12/2008 at 5:52 PM

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)

Comment 11 by Raymond Camden posted on 9/12/2008 at 6:02 PM

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.

Comment 12 by BogieCat posted on 9/12/2008 at 7:31 PM

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.

Comment 13 by Raymond Camden posted on 9/12/2008 at 7:34 PM

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.

Comment 14 by BogieCat posted on 9/12/2008 at 8:58 PM

Sorry.. Guess I mis understood. Not sure how to make it loop thru and see each person as a section...

Comment 15 by Raymond Camden posted on 9/12/2008 at 9:10 PM

Doesn't your code already loop over people? Something like

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

Just add cfdocumentsection tags in the loop.

Comment 16 by BogieCat posted on 9/12/2008 at 9:32 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...

Comment 17 by Chris Utt posted on 1/26/2009 at 8:22 PM

While it's great to see that Adobe has incorporated some PDF manipulation features into ColdFusion 8 I'm disappointed to see that they fell short in some areas an did not include more granular control like you can get with third party tools like ABCPDF, ActivePDF etc. Additionally there are large holes in their support such as when populating forms created with LiveCycle and using cfpdfformparam you cannot merge multiple PDF's without losing the embedded data.

Comment 18 by Raymond Camden posted on 1/27/2009 at 12:41 AM

I'm not sure I'd say they have large holes. Yes - there are holes, but since they sell another product that does everything, they had to make some cuts so as to know hurt their other businesses. Considering how difficult it is to make/work with PDFs in other languages, I think it's fair to give Adobe some props here.

Comment 19 by sh posted on 3/4/2009 at 9:59 PM

Please tell me what i'm doing wrong, here is the code. I appreciate it. Thanks

<cfpdf action="merge" name="MergedPDF" directory="L:\aip\web\reports">
<cfpdfparam source="rpt1_pdf.cfr">
<cfpdfparam source="rpt1.cfr">
<cfpdfparam source="rpt1.cfr">
</cfpdf>

Comment 20 by Raymond Camden posted on 3/4/2009 at 10:01 PM

cfr files aren't pdfs.

Comment 21 by sh posted on 3/4/2009 at 10:02 PM

But they are generating pdf report

Comment 22 by Raymond Camden posted on 3/4/2009 at 10:13 PM

But they aren't PDFs. Yes, they are used to generated PDFs (with CFREPORT), but you can't use them _as_ PDFs.

You would first need to run CFREPORT, generate the PDFs, and then use the cfpdf action to merge them.

Comment 23 by Eric Peterson posted on 7/28/2009 at 3:39 AM

Any idea on why the instructions for evaluating an odd or even pagenumber return an error for me: "coldfusion.runtime.Cast$NumberConversionException: The value {currentpagenumber} cannot be converted to a number"?

Comment 24 by Raymond Camden posted on 7/28/2009 at 7:12 AM

I haven't tried it myself. Can you share the code?

Comment 25 by Eric Peterson posted on 7/29/2009 at 11:30 PM

<cfdocument format="flashpaper">
<cfdocumentitem type="header">
<cfif (cfdocument.currentpagenumber mod 2) is 0>
<cfoutput>#sectionTitle#</cfoutput>
<cfelse>
<cfoutput>#chapterTitle#</cfoutput>
</cfif>
</cfdocumentitem>
...
</cfdocument>

Comment 26 by Raymond Camden posted on 12/11/2009 at 6:55 AM

Cool. I've said multiple times that there are -multiple- cool things fixed in 801 that people (including myself) miss because we typically stick with the main docs.

Comment 27 by John posted on 12/21/2009 at 8:33 PM

I have a pdf that ends with a table structure for a signature block - about 10 lines of text printed. I dont want the table to break with the pagebreak (ala Access 'keep together')
Can I protect the block from the pagebreak, causing the break to occur earlier?
Thanks in advance

Comment 28 by Eric posted on 12/22/2011 at 11:57 PM

I'm trying to figure out how to pass page number or nameddest (bookmark) to acrobat when use cfcontent to return a pdf document. We use cfcontent instead of direct links to the document because we secure them using permissions set within the application. Any ideas would be great.

Comment 29 by Raymond Camden posted on 12/23/2011 at 9:54 PM

You mean you want the PDF to open at a particular page?

Comment 30 by Raymond Camden posted on 12/23/2011 at 9:57 PM

If that -is- it, check this: http://kb2.adobe.com/cps/31...

Comment 31 by Eric posted on 12/23/2011 at 10:03 PM

Thanks Ray, that works if you go directly at the PDF in the browser URL. I'm using CFContent to server up the file, because to secure the documents they're not in a browser accessible directory.

Here's the bit of code that returns the pdf to the browser:
<cfheader name="content-disposition" value="inline; filename=#filename#" />
<cfcontent file="#rootfolder##documentfolder##filename#" type="#sttCFG.structFileCFContentType[fileExt]#" reset="yes">

I tried adding &page=3 to the file parameter of the cfcontent tag, but that didn't work, I tried the same thing for the cfheader thinking that that might be passed to acrobat when the local client was opening the file, but that didn't work either. Any ideas? Thanks again.

Comment 32 by Raymond Camden posted on 12/23/2011 at 10:06 PM

Looks like you can do this with DDX - this link is to the CF8 docs, but obviously it works in CF9.

http://livedocs.adobe.com/c...

Now - to be clear, a DDX operation will be slow (relatively slow), so I'd recommend - perhaps - keeping a copy of the PDF that opens to page N so you can serve it up next time.

Comment 33 by Raymond Camden posted on 12/23/2011 at 10:26 PM

I had some issues with the example in the CF docs. I'm writing a blog entry now that shows a full demo.

Comment 34 by Raymond Camden posted on 12/23/2011 at 10:40 PM

Here's the blog entry I did:

http://www.raymondcamden.co...

Comment 35 by Misty posted on 8/11/2012 at 12:28 PM

Hi Ray, i do not know what to say but i have a situation here:

i have a text file whose contents are the name of the person say "M Randhawa"

Now i have another PDF which is full of text and images and i have a %firstname% in that PDF

I want the text contents to be added to the PDF and replace the %firstname% with the firstname from the textfile

what can i do to achieve this

Comment 36 by Raymond Camden posted on 8/11/2012 at 6:44 PM

You can't edit text in a pdf using cfpdf. You can make a new PDF dynamically with cfdocument.

Comment 37 by misty posted on 8/11/2012 at 6:49 PM

btw there must some wa of doing this, because the pdf i already have is full of images and text, so it will cumbersome to convert that pdf to html and then again replace my text and regenrate it as pdf

seems very weird solution

Comment 38 by Raymond Camden posted on 8/11/2012 at 6:50 PM

There probably are other solutions out there - but if we are talking what is built into CF, the answer is no. You can easily do dynamic PDFs if your source is HTML you can wrap in cfdocument.