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.

One Comment

  1. 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,
    /Rolf