On a recent project I needed to be able to rotate PDF documents. I thought for sure there would be something like <cfpdf action="rotate">
, but I was surprised there was no such option. I came across ColdFusion 8’s built in DDX processing feature, and thought I would just do it with that. Turns out the limited DDX engine in ColdFusion does not include the ability to rotate.
But there is a way! The iText class libraries are bundled with ColdFusion, and they are capable of turning your PDFs on end.
Here is a function I use to rotate a PDF.
<cffunction name="rotatePDF" access="public" hint="Rotates a pdf" returntype="void">
<cfargument name="PDFFile" type="string" required="true" hint="Path to PDF file to rotate">
<cfargument name="degrees" type="numeric" required="true" hint="Amount to rotate PDF in degrees">
<cfset var outputFile = Application.tmpDir & "/" & CreateUUID()>
<cfset var errorMsgs = arrayNew(1)>
<cfif Not FileExists(Arguments.PDFFile)>
<cfthrow message="#Arguments.PDFFile# does not exist." detail="rotatePDF() called with nonexisting file.">
</cfif>
<cfif Not GetFileInfo(Arguments.PDFFile).canWrite>
<cfthrow message="#Arguments.PDFFile# is not writable." detail="rotatePDF() called with read only file.">
</cfif>
<cfscript>
degreesToRotate = javacast("int", Arguments.degrees);
try {
// cfSearching: initialize required objects once and reuse
PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
PdfNumber = createObject("java", "com.lowagie.text.pdf.PdfNumber");
// cfSearching: read in the input pdf file
reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( Arguments.PDFFile );
// cfSearching: rotate each page by the given number of degrees
for (p = 1; p LTE reader.getNumberOfPages(); p = p + 1) {
dictionary = reader.getPageN(javacast("int", p));
dictionary.put( PdfName.ROTATE, PdfNumber.init(degreesToRotate) );
//WriteOutput("Rotating page "& p &" "& degreesToRotate &" degrees <br />");
}
// cfSearching: save a copy of the rotated pdf
outStream = createObject("java", "java.io.FileOutputStream").init(outputFile);
stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init(reader, outStream);
}
catch (com.lowagie.text.DocumentException de) {
errorMsgs = de;
}
catch (java.io.IOException ioe) {
errorMsgs = ioe;
}
// cfSearching: always close the stamper
if (IsDefined("stamper")) {
stamper.close();
}
if (IsDefined("outStream")) {
outStream.close();
}
</cfscript>
<cfset FileMove(outputFile, Arguments.PDFFile)>
</cffunction>
One thing I ran into is that you cannot rotate a PDF more than once. For example if you rotate a PDF once using this method, then later try and rotate it again – nothing will happen. My theory is this happens because the rotating is being done by just setting a rotation value inside the PDF (see the dictionary.put() line). The PDF viewer then reads this flag and displays the PDF appropriately. If you do need to re-rotate a PDF after its been rotated with this method, it may be possible to read this value and then adjust it accordingly.
A thanks goes out to this cfsearch blog post, which provided the bulk of the code.
Rolf Staflin says:
I think you are right about the rotation. Bruno Lowagie, the man behind iText, wrote this code to rotate pages 90 degrees (I cut out the error handling for clarity):
PdfDictionary pageDict;
int rot;
PdfReader reader = new PdfReader(“test.pdf”);
int n = reader.getNumberOfPages();
for (int i = 1; i [ TRUNCATED FOR SOME REASON?? – sorry, – ed.]
The short version, then, is that a PdfDictionary stores values in a HashMap, so when you call
dictionary.put( PdfName.ROTATE, PdfNumber.init(degreesToRotate) );
you are replacing the old value with the new one. To implement a cumulative rotation, call dictionary.get(PdfName.ROTATE) first to get the current rotation. Then just add that number to degreesToRotate.
Cheers,
14 March 2008, 8:49 am/Rolf