Josh K. sent in an interesting problem today. He was generating 10 PDFs in a request, all running via cfthread, and he was trying to create one main PDF by merging the 10 he created. This turned out to be a bit difficult at first, but here is the code I ended up with that worked. Let's break it down line by line.
First, let's create a list to store the names of our threads. Because our final goal is to merge the PDFs, we know that at some point we have to join the threads. To join threads, you have to know their names. So our list variable will remember each thread name.1 <cfset threadList = "">
Now let's loop. I set my loop to 5 instead of 10 just to make things a bit quicker:
1 <!--- loop pages and create cfdocuments --->
2 <cfloop index="x" from="1" to="5">
3 <cfset name = "thread_number_#x#">
4 <cfset threadList = listAppend(threadList,name)>
For each loop iteration, we create a name for the thread and append it to the list. Now for the actual thread.
2 <cfloop index="x" from="1" to="5">
3 <cfset name = "thread_number_#x#">
4 <cfset threadList = listAppend(threadList,name)>
1 <cfthread name="#name#">
2
3 <cfdocument name="thread.pdf" format="pdf">
4
5 <!--- output data --->
6 <cfoutput>#randRange(1,10000)#pdf data boo yah #thread.name#</cfoutput>
7
8 </cfdocument>
9
10 </cfthread>
You can see our dynamic thread name being used there. The cfdocument tag creates the PDF and stores it in a variable called thread.pdf. Normally variables you create in a thread exist in a local scope not available outside the thread. The Thread scope doesn't have this problem. Later on when I join my threads I can reuse this data. By naming my PDF variable thread.pdf, I'm simply storing the data in the thread scope ColdFusion created for me.
Now for the next block:
2
3 <cfdocument name="thread.pdf" format="pdf">
4
5 <!--- output data --->
6 <cfoutput>#randRange(1,10000)#pdf data boo yah #thread.name#</cfoutput>
7
8 </cfdocument>
9
10 </cfthread>
1 </cfloop>
Yeah, that ends the loop. Real complex block. ;)
Ok, so now that we've created our threads that generate PDFs, we have to wait for them to complete:
1 <cfthread action="join" name="#threadlist#" />
That's it. Nice and simple. While my 5 threads operate in parallel, this tag will force ColdFusion to wait for them to finish. Now for the merge:
1 <cfpdf action="merge" name="final">
2 <cfloop list="#threadlist#" index="i">
3 <cfset tempCopy = cfthread[i].pdf>
4 <cfpdfparam source="tempCopy">
5 </cfloop>
6 </cfpdf>
7 <cfheader name="Content-Disposition" value="inline; filename=test.pdf">
8 <cfcontent type="application/pdf" variable="#tobinary(final)#" reset="No" >
I begin with a cfpdf tag, merge action. This will perform the actual merge. The tag allows you to use the child tag, cfpdfparam, to specify PDFs to merge. You can use either a file path or the name of a PDF variable. I tried specifiying source="cfthread[i].pdf", but this confused the tag. It thought it was a file path. The tempCopy variable simply copies out the data and makes cfpdfparam nice and happy. (Let me state - I really dislike it when ColdFusion uses the names of variables as arguments. cfoutput query="name" is a good example of this. I really wish it would allow for cfoutput query="#qry#". This is a perfect example. Adobe had to build code to say - is this string a path or a variable? It would have been far easier to say, if a string, assume a path, otherwise assume it's binary data.)
One done with the merge, a quick cfheader/cfcontent combo will actually serve the PDF up to the user.
As always, hope this is helpful! Complete source code is below.
2 <cfloop list="#threadlist#" index="i">
3 <cfset tempCopy = cfthread[i].pdf>
4 <cfpdfparam source="tempCopy">
5 </cfloop>
6 </cfpdf>
7 <cfheader name="Content-Disposition" value="inline; filename=test.pdf">
8 <cfcontent type="application/pdf" variable="#tobinary(final)#" reset="No" >
1 <cfset threadList = "">
2
3 <!--- loop pages and create cfdocuments --->
4 <cfloop index="x" from="1" to="5">
5 <cfset name = "thread_number_#x#">
6 <cfset threadList = listAppend(threadList,name)>
7 <cfthread name="#name#" >
8
9 <cfdocument name="thread.pdf" format="pdf">
10
11 <!--- output data --->
12 <cfoutput>#randRange(1,10000)#pdf data boo yah #thread.name#</cfoutput>
13
14 </cfdocument>
15
16 </cfthread>
17
18 </cfloop>
19
20 <cfthread action="join" name="#threadlist#" />
21
22 <cfpdf action="merge" name="final">
23 <cfloop list="#threadlist#" index="i">
24 <cfset tempCopy = cfthread[i].pdf>
25 <cfpdfparam source="tempCopy">
26 </cfloop>
27 </cfpdf>
28 <cfheader name="Content-Disposition" value="inline; filename=test.pdf">
29 <cfcontent type="application/pdf" variable="#tobinary(final)#" reset="No">
2
3 <!--- loop pages and create cfdocuments --->
4 <cfloop index="x" from="1" to="5">
5 <cfset name = "thread_number_#x#">
6 <cfset threadList = listAppend(threadList,name)>
7 <cfthread name="#name#" >
8
9 <cfdocument name="thread.pdf" format="pdf">
10
11 <!--- output data --->
12 <cfoutput>#randRange(1,10000)#pdf data boo yah #thread.name#</cfoutput>
13
14 </cfdocument>
15
16 </cfthread>
17
18 </cfloop>
19
20 <cfthread action="join" name="#threadlist#" />
21
22 <cfpdf action="merge" name="final">
23 <cfloop list="#threadlist#" index="i">
24 <cfset tempCopy = cfthread[i].pdf>
25 <cfpdfparam source="tempCopy">
26 </cfloop>
27 </cfpdf>
28 <cfheader name="Content-Disposition" value="inline; filename=test.pdf">
29 <cfcontent type="application/pdf" variable="#tobinary(final)#" reset="No">
Comment 1 written by Josh Knutson on 17 June 2009, at 11:08 PM
Comment 2 written by chris hough on 13 September 2009, at 1:38 AM
i have 1 question though, have u seen any performance issues with the merge functionality slowing down the server? just curious.
Comment 3 written by Raymond Camden on 13 September 2009, at 9:27 AM
Comment 4 written by Sanjeev on 8 October 2009, at 11:34 AM
How can i modify this to use for a cfpdfform tag ? I have a bunch of pdf's with form in side then and based on condition i have to fill those forms[pdf] and merge all of them together.
Comment 5 written by Raymond Camden on 12 October 2009, at 10:21 PM
Comment 6 written by Sanjeev on 20 October 2009, at 9:30 AM
Yeah, I tried the code writing in myself. But am facing a weird problem. I was trying to place the 'populated' pdfform inside the document tag to generate a 'merged' pdf.this whole thing is threaded.
I followed the help docs and placed the documentsection also at the same level as pdfform. But the resultant pdf is empty!!
<cfdocument format="pdf">
<cfpdfform source="mypdf.pdf" action="populate">
<cfpdfsubform name="myform">
<cfpdfformparam name="feild1" value="value1">
<cfpdfformparam name="field2" value="value2">
</cfpdfsubform>
</cfpdfform>
<cfdocumentsection/>
</cfdocument>
Just beating the head. I think I may be doing something basic wrong. But as of now.. I'm in the darkness still.
Comment 7 written by Raymond Camden on 20 October 2009, at 11:06 AM
Comment 8 written by Sanjeev on 21 October 2009, at 7:10 AM
I did that. But all i see is a empty pdf. Just for me the driving Question is how do I save a populated pdf form into a variable, so that i can use it later.
Also Ray,
Is there a way to find what is the 'form name' present inside the interactive pdf? I tried DDX DocumentInformation Tag and found it to be a 'AcroForm' formtype, but could not see any promising result info about form name. Any thoughts?
Comment 9 written by Raymond Camden on 21 October 2009, at 8:08 AM
As to your part 2 - check out the docs on cfpdfform. It may be your answer. I've not played with that so I can't help you there.
Comment 10 written by Sanjeev on 22 October 2009, at 12:17 AM
[Add Comment] [Subscribe to Comments]