Posted in ColdFusion | Posted on 06-17-2009 | 4,341 views
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.
Now let's loop. I set my loop to 5 instead of 10 just to make things a bit quicker:
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
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:
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:
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:
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
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">


i have 1 question though, have u seen any performance issues with the merge functionality slowing down the server? just curious.
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.
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.
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?
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.
I tried this code sample and it gives me an error.
Element PDF is undefined in a Java object of type class coldfusion.thread.ThreadScope.
on line <cfset tempCopy = cfthread[i].pdf>
Any idea?
Thanks,
<!--- Read in the Stark Images watermark. --->
<cfset objWatermark = ImageNew( "./images/logoMain.png" ) />
<!---Resize watermark image--->
<cfimage action = "resize" height = "75%" source = "#objWatermark#" width = "75%" overwrite = "no" name = "objWatermarkresize">
<!---Rotate watermark--->
<cfimage action = "rotate" angle = "-35" source="#objWatermarkresize#" overwrite="no" name="objWatermarkrotate">
<cfoutput query="rsProofs" startrow="#StartRow_rsProofs#" maxrows="#MaxRows_rsProofs#">
<!--- Read in the original image. --->
<cfset objImage = ImageRead( "./proof/#rsProofs.FILENAME#" ) />
<!---Resize main image--->
<cfimage action = "resize" height = "10%" source = "#objImage#" width = "10%" overwrite = "no" name = "objImageresize">
<!---
Turn on antialiasing on the existing image
for the pasting to render nicely.
--->
<cfset ImageSetAntialiasing( objImageresize, "on" ) />
<!---
When we paste the watermark onto the photo, we don't
want it to be fully visible. Therefore, let's set the
drawing transparency to 50% before we paste.
--->
<cfset ImageSetDrawingTransparency( objImageresize, 60 ) />
<!---
Paste the watermark on to the image. We are going
to paste this into the bottom, right corner.
--->
<cfset ImagePaste( objImageresize, objWatermarkrotate, 30, 0 ) />
<!--- Write it to the browser. --->
<p><cfimage action="writetobrowser" source="#objImageresize#" quality="0.10" /></p>
<hr />
</cfoutput>
<a href="<cfoutput>#CurrentPage#?PageNum_rsProofs=#Min(IncrementValue(PageNum_rsProofs),TotalPages_rsProofs)##QueryString_rsProofs#</cfoutput>">Next</a>
[Add Comment] [Subscribe to Comments]