Showing posts with label iText. Show all posts
Showing posts with label iText. Show all posts

Monday, April 19, 2010

ColdFusion: iText / Add JavaScript To Form Example

I occasionally see questions about adding javascript to existing pdf's. There are (of course) some great examples on the iText site. I thought this CF translation of the AddJavaScriptToForm example might be helpful. If you review the code and comments, it actually simpler than it looks. There are some minor differences to compensate for the older iText jars in CF9 and CF8. As well as some name changes to avoid reserved word conflicts.

Source: http://1t3xt.info/examples/browse/?page=example&id=438


Javascript Code (CF8 + CF9)

<cfsavecontent variable="jsCode">
    function setReadOnly(readonly) {
        var partner = this.getField('partner');
        if(readonly) {
            partner.value = '';
        }
        partner.readonly = readonly;
    }
    function validate() {
        var married = this.getField('married');
        var partner = this.getField('partner');
        if (married.value == 'yes' && partner.value == '') {
            app.alert('please enter the name of your partner');
        }
        else {
            var prop = new Object();
            prop.cURL = "http://1t3xt.info/examples/request.php";
            prop.cSubmitAs = "HTML";
            this.submitForm( {  
                                cURL: "http://1t3xt.info/examples/request.php",
                                cSubmitAs: "HTML"
                              } 
                            );            
        }
    }
</cfsavecontent>

1) Create a Form (CF9 + CF8)
Note: CF8 does not support "finally". So for compatibility, just change the try/finally clause to a try/catch.
<cfscript>
    outputPath     = ExpandPath("form_without_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();

    try {
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        writer = createObject("java", "com.lowagie.text.pdf.PdfWriter").getInstance(document, stream);
        document.open();
         
         Element = createObject("java", "com.lowagie.text.Element");
        BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
        //Note: CF8 + CF9 iText versions do not have a createFont() method with zero params
        bf = BaseFont.createFont( BaseFont.HELVETICA, BaseFont.WINANSI, false );
        directcontent = writer.getDirectContent();
        directcontent.beginText();
        directcontent.setFontAndSize(bf, 12);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "Married?", 36, 770, 0 );
        directcontent.showTextAligned( Element.ALIGN_LEFT, "YES", 58, 750, 0);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "NO", 102, 750, 0);
        directcontent.showTextAligned( Element.ALIGN_LEFT, "Name partner?", 36, 730, 0 );
        directcontent.endText();

        // initialize reusable objects
         Rectangle = createObject("java", "com.lowagie.text.Rectangle");
         PdfFormField = createObject("java", "com.lowagie.text.pdf.PdfFormField");
         RadioCheckField = createObject("java", "com.lowagie.text.pdf.RadioCheckField");
         TextField = createObject("java", "com.lowagie.text.pdf.TextField");
         Color = createObject("java", "java.awt.Color");

        married = PdfFormField.createRadioButton(writer, true);
        married.setFieldName("married");
        writer.addAnnotation( married );
        
        // Note: Field names changed to avoid CF reserved word conflicts (ie "yes", "no")
        married.setValueAsName("yes");
        rectYes = Rectangle.init( 40, 766, 56, 744 );
        yesField = RadioCheckField.init(writer, rectYes, javacast("null", ""), "yes");
        yesField.setChecked(true);
        married.addKid( yesField.getRadioField() );
        rectNo = Rectangle.init( 84, 766, 100, 744 );
        noField = RadioCheckField.init(writer, rectNo, javacast("null", ""), "no");
        noField.setChecked(false);
        married.addKid( noField.getRadioField() );
        writer.addAnnotation( married );
 
         rect = Rectangle.init( 40, 710, 200, 726 );
        partner = TextField.init( writer, rect, "partner" );
        partner.setBorderColor( Color.BLACK );
        partner.setBorderWidth( 0.5 );
        writer.addAnnotation( partner.getTextField() );
    
        document.close();
        WriteOutput("File created! "& outputPath &"<hr>");
    }
       // cleanup  
    finally {
        if (isDefined("document")) {
            document.close();
        }        
        if (isDefined("stream")) {
            stream.close();
        }        
    }
</cfscript>

2) Add the Javascript (CF9 Only)
<cfscript>
    inputPath     = ExpandPath("form_without_js.pdf");
    outputPath     = ExpandPath("form_plus_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();
    
    try {
        // read in the pdf form
        reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, stream );

        // add javascript functions to the document
        stamper.getWriter().addJavaScript( jsCode );

        // create reference objects
        PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
        PdfAction = createObject("java", "com.lowagie.text.pdf.PdfAction");
        PdfDictionary = createObject("java", "com.lowagie.text.pdf.PdfDictionary");
        PushbuttonField = createObject("java", "com.lowagie.text.pdf.PushbuttonField");
        
        // extract the parent option from the form 
        formObj = stamper.getAcroFields();
        fd = formObj.getFieldItem("married");

        // retrieve the dictionaries for "yes" radio button
        // note: CF9 iText version does not have the getWidgetRef(index) method
         dictYes = reader.getPdfObject( fd.widget_refs.get(0) );
        yesAction = dictYes.getAsDict( PdfName.AA );
        if (not IsDefined("yesAction")) {
            yesAction = PdfDictionary.init();
        }
        // add an onFocus event to this field
        yesAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(false);", stamper.getWriter()));
        dictYes.put( PdfName.AA, yesAction );

        // retrieve the dictionaries for "no" radio button
        dictNo = reader.getPdfObject( fd.widget_refs.get(1));
        noAction = dictNo.getAsDict( PdfName.AA );
        if (not IsDefined("noAction")) {
            noAction = PdfDictionary.init();
        }    
        // add an onFocus event to this field
        noAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(true);", stamper.getWriter()));
        dictNo.put(PdfName.AA, noAction);
 
         // create a submit button
        writer = stamper.getWriter();
        button = PushbuttonField.init(    writer, Rectangle.init(40, 690, 200, 710), "submit" );
        button.setText( "validate and submit" );
        button.setOptions( PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT );
        validateAndSubmit = button.getField();
        // this will call the validate function when the button is clicked
        validateAndSubmit.setAction( PdfAction.javaScript("validate();", stamper.getWriter()) );
        // add the button to page 1
        stamper.addAnnotation(validateAndSubmit, 1);

        WriteOutput("File created! "& outputPath &"<hr>");
    }
    finally {
        // cleanup
        if (isDefined("stamper")) {
            stamper.close();
        }        
        if (isDefined("stream")) {
            stream.close();
        }        
    }
</cfscript>

2) Add the Javascript (CF8 Only)
<cfscript>
    inputPath     = ExpandPath("form_without_js.pdf");
    outputPath     = ExpandPath("form_plus_js.pdf");
    document = createObject("java", "com.lowagie.text.Document").init();
    
    try {
        // read in the pdf form
        reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
        stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, stream );

        // add javascript functions to the document
        stamper.getWriter().addJavaScript( jsCode );

        // create reference objects
        PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
        PdfAction = createObject("java", "com.lowagie.text.pdf.PdfAction");
        PdfDictionary = createObject("java", "com.lowagie.text.pdf.PdfDictionary");
        PushbuttonField = createObject("java", "com.lowagie.text.pdf.PushbuttonField");
        
        // prepare to extract form field objects
        formObj = stamper.getAcroFields();
        
        // get parent option
        fd = formObj.getFieldItem("married");

        // retrieve the dictionaries for "yes" radio button
        // note: CF8 iText version does not have the getWidgetRef(index) method or getDirectObject()
         dictYes = reader.getPdfObject( fd.widget_refs.get(0) );
        yesAction = reader.getPdfObject(dictYes.get(PdfName.AA));
        if (not IsDefined("yesAction") or not yesAction.isDictionary()) {
            yesAction = PdfDictionary.init();
        }
        // add an onFocus event to this field
        yesAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(false);", stamper.getWriter()));
        dictYes.put( PdfName.AA, yesAction );

        // retrieve the dictionaries for "no" radio button
        dictNo = reader.getPdfObject( fd.widget_refs.get(1));
        noAction = reader.getPdfObject( dictNo.get(PdfName.AA) );
        if (not IsDefined("noAction") or not noAction.isDictionary()) {
            noAction = PdfDictionary.init();
        }    
        // add an onFocus event to this field
        noAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(true);", stamper.getWriter()));
        dictNo.put(PdfName.AA, noAction);
 
         // create a submit button
        writer = stamper.getWriter();
        button = PushbuttonField.init(    writer,
                                        Rectangle.init(40, 690, 200, 710), 
                                        "submit"
                                    );
        button.setText( "validate and submit" );
        button.setOptions( PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT );
        validateAndSubmit = button.getField();
        // this will cal the validate function when the button is clicked
        validateAndSubmit.setAction( PdfAction.javaScript("validate();", stamper.getWriter()) );
        // add the button to page 1
        stamper.addAnnotation(validateAndSubmit, 1);

        WriteOutput("File created! "& outputPath &"<hr>");
    }
    catch(Any e) {
        WriteOutput("ERROR: "& e.message);
    }
    if (isDefined("stamper")) {
        stamper.close();
    }        
    if (isDefined("stream")) {
        stream.close();
    }        
</cfscript>

...Read More

Thursday, February 11, 2010

ColdFusion: Adding a Link to an Existing PDF with iText

A recent question on stackoverflow.com asked how to add a hyperlink to an existing pdf with iText. There is most definitely more than one way to do it, and quite possibly better methods than the one mentioned here. But as it uses a few interesting techniques I thought I would share it.  Of course any comments or improvements are always welcome.


As usual, first open the source pdf with a reader object, and use a stamper to prepare the output file for writing. With that out of the way, you can move on to creating the hyperlink.

Given that CF8 and CF9 use older versions of iText, I decided the simplest method would be to use a Chunk object.  If you are not familiar with Chunks, they are a low level object used to represent a bunch of characters all having the same properties (font, color, etcetera). So first initialize a Chunk with whatever text you want to use for the link. Then use setAnchor() to specify the link url.

<cfscript>
   pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
   outStream = createObject("java", "java.io.FileOutputStream").init( outputPath );
   pdfStamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( pdfReader, outStream );

   chunk = createObject("java", "com.lowagie.text.Chunk").init("Rage Against the Machine (on Wikipedia)");
   chunk.setAnchor("http://en.wikipedia.org/wiki/Rage_Against_the_Machine");
</cfscript>

Since the default font is pretty bland, you will probably want to select a different font for the link. There are several ways to work with fonts. But in this example I defined a BaseFont by passing in the path to the physical font file, the desired encoding and a flag to ensure the font is embedded. The BaseFont definition is then used to create a Font object with the desired settings (such as size, color and style) and applied to the Chunk object.


(On a side note, I was going to use the basic arial.ttf font. But while perusing the windows/font directory, I was amused to find an odd font named Rage and promptly decided I had use it for this entry instead)


<cfscript>
   // define an embedded font 
   BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
   Font = createObject("java", "com.lowagie.text.Font");
   bf = BaseFont.createFont("c:/windows/fonts/rage.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);
   // create the main font object
   textColor = createObject("java", "java.awt.Color").decode("##084f5a");
   textFont = Font.init(bf, 18, Font.UNDERLINE, textColor);   
   chunk.setFont( textFont );
</cfscript>


Now to position the Chunk, I decided to use a ColumnText object. As the name implies, it is used to layout text in column format. Though used quite simply here, the ColumnText class is capable of some pretty complex operations.

To create a ColumnText object you must pass in a PdfContentByte object. In loose terms that is the canvas where the text will be drawn.  In this example, the link is added to the foreground. So getOverContent() is used to grab the canvas of the target page from the stamper object and then passed into the ColumnText object.  Finally the Chunk is added to the ColumnText object for rendering.

The next to last step is to define the dimensions of the column. Once the dimensions are defined, ColumnText.go() is used to draw the link onto the pdf.  (I will not go into the details of positioning here. But if you are unfamiliar with it, this entry on buttons describes the typical way in which objects are positioned in iText.)

Note: This snippet uses deprecated methods for CF8 compatibility. For a CF9 compatible version, see the end of entry

<cfscript>
   cb = pdfStamper.getOverContent(1); 
   ct = createObject("java", "com.lowagie.text.pdf.ColumnText").init(cb);
   ct.addElement( chunk );

   // set the column dimensions
   page = pdfReader.getPageSize(1);
   llx =  page.right()- 325;   
   lly = page.top() - 36;       
   urx = page.right();                
   ury = page.top() - 8;     
   ct.setSimpleColumn(llx, lly, urx, ury);

   // write the text
   ct.go();
</cfscript>

Once you close the stamper, the resulting pdf should contain a cool looking link in the top right. Minus the thematic image of course ..


Complete Code (ColdFusion 8)
<cfscript>
     inputPath = ExpandPath("./myDocument.pdf");
     outputPath = ExpandPath("./myDocumentWithLink.pdf");

     try {
        // initialize objects
        pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
        outStream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        pdfStamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( pdfReader, outStream );

        // create a chunk with a anchor (ie hyperlink)
        chunk = createObject("java", "com.lowagie.text.Chunk").init("Rage Against the Machine (on Wikipedia)");
        chunk.setAnchor("http://en.wikipedia.org/wiki/Rage_Against_the_Machine");

        // define an embedded font 
        BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
        Font = createObject("java", "com.lowagie.text.Font");
        bf = BaseFont.createFont("c:/windows/fonts/rage.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);

        // create the main font object
        textColor = createObject("java", "java.awt.Color").decode("##084f5a");
        textFont = Font.init(bf, 18, Font.UNDERLINE, textColor);   

        // apply the font to the chunk 
        chunk.setFont( textFont );

        // prepare to write the link onto the *first* page only        
        cb = pdfStamper.getOverContent(1); // first page
        ct = createObject("java", "com.lowagie.text.pdf.ColumnText").init(cb);
        ct.addElement( chunk );

        // position link near top right 
        // note: using deprecated versions of getRight() and getBottom()
        page = pdfReader.getPageSize(1);
        llx =  page.right()- 325;   
        lly = page.top() - 36;       
        urx = page.right();                
        ury = page.top() - 8;     
        // initialize column dimensions
        ct.setSimpleColumn(llx, lly, urx, ury);

        // write the text
        ct.go();
    }
    catch (java.lang.Exception e) {
       // Save the error object and use cfdump _outside_ 
       // the cfscript block to display the full error detail
       WriteOutput("ERROR: "& e.message &"<hr />");
       WriteOutput("DETAIL: "& e.detail);
    }        
   
   // closing the stamper generates the output file
    if (IsDefined("pdfStamper")) {
        WriteOutput("Closing pdfStamper ..<hr />");
       pdfStamper.close();
   }
   // also ensure the outstream is always closed
   // to avoid locked files if an error occurs early on ..
    if (IsDefined("outStream")) {
        WriteOutput("Closing outStream  ..<hr />");
       outStream.close();
   }
   WriteOutput("Output file generated: "& outputPath );
</cfscript>

Complete Code (ColdFusion 9)
<cfscript>
     inputPath = ExpandPath("./myDocument.pdf");
     outputPath = ExpandPath("./myDocumentWithLink.pdf");

     try {
        // initialize objects
        pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
        outStream = createObject("java", "java.io.FileOutputStream").init( outputPath );
        pdfStamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( pdfReader, outStream );

        // create a chunk with a anchor (ie hyperlink)
        chunk = createObject("java", "com.lowagie.text.Chunk").init("Rage Against the Machine (on Wikipedia)");
        chunk.setAnchor("http://en.wikipedia.org/wiki/Rage_Against_the_Machine");

        // define an embedded font 
        BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
        Font = createObject("java", "com.lowagie.text.Font");
        bf = BaseFont.createFont("c:/windows/fonts/rage.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);

        // create the main font object
        textColor = createObject("java", "java.awt.Color").decode("##084f5a");
        textFont = Font.init(bf, 18, Font.UNDERLINE, textColor);   

        // apply the font to the chunk 
        chunk.setFont( textFont );

        // prepare to write the link onto the *first* page only        
        cb = pdfStamper.getOverContent(1); // first page
        ct = createObject("java", "com.lowagie.text.pdf.ColumnText").init(cb);
        ct.addElement( chunk );

        // position link near top right 
        // note: using deprecated versions of getRight() and getBottom()
        page = pdfReader.getPageSize(1);
        llx =  page.getRight()- 325;   
        lly = page.getTop() - 36;       
        urx = page.getRight();                
        ury = page.getTop() - 8;     
        // initialize column dimensions
        ct.setSimpleColumn(llx, lly, urx, ury);

        // write the text
        ct.go();
    }
    catch (java.lang.Exception e) {
       // Save the error object and use cfdump _outside_ 
       // the cfscript block to display the full error detail
       WriteOutput("ERROR: "& e.message &"<hr />");
       WriteOutput("DETAIL: "& e.detail);
    }        
   
   // closing the stamper generates the output file
    if (IsDefined("pdfStamper")) {
        WriteOutput("Closing pdfStamper ..<hr />");
       pdfStamper.close();
   }
   // also ensure the outstream is always closed
   // to avoid locked files if an error occurs early on ..
    if (IsDefined("outStream")) {
        WriteOutput("Closing outStream  ..<hr />");
       outStream.close();
   }
   WriteOutput("Output file generated: "& outputPath );
</cfscript>

...Read More

ColdFusion 9: Adding Document Level Attachments to a PDF with iText

While cfpdf provides some nice features, like merging and deleting pages, it does not provide a way to attach files as far as I know. But this can be done with a small bit of iText magic. There are a few different ways to attach files to a pdf. If you are looking for examples, there are some excellent ones on the iText site you can easily adapt for ColdFusion.


One way to attach a file is at the document level. So when viewing the pdf with a tool like Adobe Reader, the files appear only in the attachments pane. The iText version that ships with CF9 has a handy convenience method for creating attachments. Unfortunately, that method did not exist yet in iText 1.4. So if you are running CF8, you will need to use the JavaLoader.cfc, and a newer version of iText, to take advantage of it. (Though a quick glance at the source code suggests it is possible with CF8's version with a little extra CF code.) But back to the example..

In case you do not have a pdf handy, simply create one with cfdocument first. Though any pdf should work.

<cfset inputFile = ExpandPath("myDocument.pdf") />
<cfdocument format="PDF" name="pdfContent">
    <cfdump var="#server.os#" label="Server O/S" />
</cfdocument>

Now if you have read any of my previous entries on iText, you will be very familiar with next few steps. Initialize a few variables with the paths of the files you wish to attach. (I went a little crazy and decided to attach three files: a Word document, an Excel file and a simple text file). Next, read in your source file with a PdfReader object. Then prepare a PdfStamper to generate the output file.

<cfscript>
   // ...
   inputFile = ExpandPath("myDocument.pdf");
   outputFile = ExpandPath("myDocumentWithAttachments.pdf");
   
   attach1   = "c:/test/docs/NewsLetter-Feb-2010.doc";
   attach2   = "c:/test/docs/Statistics-Jan-2010.xls";
   attach3   = "c:/test/docs/test.txt";

   reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputFile );
   outStream = createObject("java", "java.io.FileOutputStream").init( outputFile );
   stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, outStream );
   // ...
</cfscript>

It is worth noting this example actually embeds the content of each file within the pdf. So obviously final pdf will be larger than the original. To actually attach the files, simply use the stamper's addFileAttachment(..) method on each one. That method is overloaded, but the signature used in this example accepts four arguments: file description, data, file path and file name.

The first argument is an optional description of the attachment. The second and third arguments pertain to how you wish to attach the content. You can either supply content dynamically (via an array of bytes) or using a physical file path. Since it would not make sense to supply both, just provide a value for one of the arguments and use null for the other. If you accidentally supply both, the file path will probably be ignored.


The final argument of addFileAttachment(..) allows you to customize the file name displayed in the attachment pane. Obviously a handy feature if you are supplying dynamic content and do not necessarily have a file name. But you can use it in either scenario to just display a more user friendly file name. (On a side note, the file name is technically optional. But I do not see much point in leaving it blank.)

<cfscript>
   // ...
   // create the first attachment from a file path
   stamper.addFileAttachment("Newsletter for February", javacast("null", ""), attach1, "Newsletter.doc");

   // ...
</cfscript>

Once you have attached the files, you can get a little fancy and modify the viewer preferences to display the attachment pane when the pdf is first opened. It is a nice way to draw a user's attention to the fact that the pdf contains attachments. You should also adjust the pdf version to 1.6, since that is when this feature was added.

<cfscript>
   // ...
   // display the attachment pane when the pdf opens (Since 1.6)  
   writer = stamper.getWriter();
   writer.setPdfVersion( writer.VERSION_1_6 );
   stamper.setViewerPreferences( writer.PageModeUseAttachments );    
   // ...
</cfscript>

Once you have properly closed the pdf, the final output should look like the image below in Acrobat Reader. Now whether or not you can open/save the attachments all depends on your security settings. I believe the default for Acrobat Reader 8 and 9 is to disable the opening of all non-pdf attachments. So you may need to adjust your Trust Manager settings accordingly. Keep in mind that behavior has nothing to do with the pdf file itself. It is strictly how Acrobat chooses to handle attachments.



On a closing note, if you are not up to date on your patches, be aware there are some relatively recent security updates for both Adobe Reader and Acrobat, involving the Trust Manager. So if you have not checked your version recently, now might be a good time to do so!


Complete Code
<!---
    Create test PDF
--->
<cfset inputFile = ExpandPath("myDocument.pdf") />
<cfdocument format="PDF" name="pdfContent">
    <cfdump var="#server.os#" label="Server O/S" />
</cfdocument>

<!---
    Add attachments to existing pdf
--->
<cfscript>
    try {
        // Source/destination file paths
        inputFile = ExpandPath("myDocument.pdf");
        outputFile = ExpandPath("myDocumentWithAttachments.pdf");
        
        // Sample files to attach to pdf
        attach1     = "c:/test/docs/NewsLetter-Feb-2010.doc";
        attach2        = "c:/test/docs/Statistics-Jan-2010.xls";
        attach3        = "c:/test/docs/test.txt";

        // open source file and prepare for modification
        reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputFile );
        outStream = createObject("java", "java.io.FileOutputStream").init( outputFile );
        stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, outStream );

        // create the first attachment from a file path
        stamper.addFileAttachment("Newsletter for February", javacast("null", ""), attach1, "Newsletter.doc");

        // create the second "dynamically" (ie from an array of bytes). 
        // deliberately leave the description blank
        bytes = FileReadBinary(attach2);
        stamper.addFileAttachment(javacast("null", ""), bytes, javacast("null", ""), "Statistics.xls");

        // create the last attachment from a file path
        stamper.addFileAttachment("Meaningless text file", javacast("null", ""), attach3, "Stuff.txt");

        // display the attachment pane when the pdf opens (Since 1.6)  
        writer = stamper.getWriter();
        writer.setPdfVersion( writer.VERSION_1_6 );
        stamper.setViewerPreferences( writer.PageModeUseAttachments );    
            
    }
    finally {
        // always cleanup objects
        if (IsDefined("stamper")) {
               stamper.close();
        }
          if (IsDefined("outStream")) {
              outStream.close();
          }
    }

    WriteOutput("Output saved to file "& outputFile);
</cfscript>

...Read More

Saturday, January 16, 2010

ColdFusion + iText 5.0.0 (Good things come in *new* packages)

In case you were unaware, iText 5.0.0 was released a short while ago, and a new iText book scheduled for release in June. The new version of iText contains some significant changes, including a new license. But to ColdFusion users, the most signficant change may be the new package name.


Most everyone knows ColdFusion uses an older version of iText internally. So upgrading to a newer version (without breaking ColdFusion) requires a little fancy foot-work, such as using a custom class loader like JavaLoader.cfc. But with the new package name, that has all changed.

In previous versions, iText classes were packaged under com.lowagie.* . As of version 5.0.0, the classes are now packaged under a new name: com.itextpdf.*. This change is a huge benefit to ColdFusion users because it eliminates class path compatibility issues with MX7, CF8 and CF9. The new iText version should happily co-exist with the older iText versions shipped with MX7, CF8 and CF9.

Now obviously using version 5.0.0 means changing code to use the new package name, and being careful not to mix old and new. But it is now easier than ever before to use the latest version of iText under ColdFusion.

...Read More

Tuesday, November 3, 2009

A Better PdfPageEventHandler with JavaLoader/CFCDynamicProxy

Yesterday , I wrote about discovering a true gem in latest version of Mark Mandel's JavaLoader: the CFCDynamicProxy. This tiny, but powerful, class acts as a wrapper around a ColdFusion cfc. Essentially allowing it to mimic a native java object and communicate directly with other java objects in ways it never could before! We all know just about everything in ColdFusion boils down to a java object internally. But until now there were certain things you just could not do from a cfc without taking the r-e-a-l-l-y long way around. Well, the CFCDynamicProxy changes all that.


A perfect example is page events in iText. In order to add custom headers, footers, etcetera with iText you need a custom java class that implements the PdfPageEvent interface. Well as easy and natural as that is in a java environment, it is a bit awkward to have to create and load a new java class in CF every time you wish to create a different set of headers or footers.

Eventually, I came up with a more dynamic method of calling a cffunction to add headers/footers from a java, but it still required that custom java class. But using the CFCDynamicProxy, I was able to simplify and rewrite the code entirely in CF, and let me say... man what a difference! It is now totally dynamic and written in native CF, no extra java classes needed. In comparison to the elegance of using the new Proxy, my previous attempt looks like a decrepit, wart-nosed-hag. To be fair, a lot of what the previous entry was doing manually is still going on in the background. But
the CFCDynamicProxy makes it much, much simpler.

The first step in my code beautification effort was creating a CFC that implemented all of the methods in the iText PdfPageEvent interface. Just as you would if you were implementing a CF interface. Using the PdfPageEvent API, I created a cfc with a function for each of the methods in that interface, taking care to properly align the arguments and data types so they matched their java counterparts.

Mapping the java to ColdFusion data types is pretty simple. Any arguments that are instances of a java class (like PdfWriter, Document, Rectangle) are all mapped to type="any". The rest are usually primitive types like "string" and "float" and the conversions are intuitive, for the most part.






Next I filled in a few functions needed for generating basic page footers. The key function was onPageEnd, which is where all the action takes place. OnPageEnd will be called by iText just before new pages are written to my new pdf document. So inside that function, I grab the writer object and use it to set the desired properties and finally write the footer text onto the pdf.


<cffunction name="onEndPage" access="public" returntype="void" output="true"
hint="Called when a page is finished, just before being written to the document.">
<cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
<cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
<cfset var Local = {} />

<cfscript>
if (len(variables.instance.footerText))
{
Local.cb = arguments.writer.getDirectContent();
Local.cb.saveState();

Local.cb.beginText();
Local.cb.setColorFill( variables.instance.textColor );
Local.cb.setFontAndSize( variables.instance.font, javacast("float", variables.instance.fontSize) );
Local.cb.setTextMatrix( arguments.document.left(), arguments.document.bottom() - 10);
Local.text = variables.instance.footerText &" page ["& arguments.writer.getPageNumber() &"]";
Local.cb.showText( Local.text );
Local.cb.endText();

Local.cb.restoreState();
}
</cfscript>

</cffunction>


Having completed my faux-java-class/cfc, the next step was using the CFCDynamicProxy to see if this stuff actually worked. Using the Dynamic proxy is like using any other jar with the JavaLoader. You just add the new cfcdynamicproxy.jar to the array of paths. Then create an instance of the JavaLoader, except this time you set the loadColdFusionClassPath parameter equal to true. This allows the JavaLoader to access ColdFusion's classes to create the proxy object.

Note: The code example included with JavaLoader 1.0 was very easy to follow. So my base code is ripped straight out of Mark's example.



<cfscript>
//add the javaloader dynamic proxy library (and the iText jar) to the javaloader
libpaths = [];
arrayAppend(libpaths, expandPath("/javaLoader/support/cfcdynamicproxy/lib/cfcdynamicproxy.jar"));
arrayAppend(libpaths, expandPath("/dev/itext/iText-2.1.7.jar") );

//we HAVE to load the ColdFusion class path to use the dynamic proxy, as it uses ColdFusion's classes
loader = createObject("component", "javaLoader.JavaLoader").init(loadPaths=libpaths, loadColdFusionClassPath=true);
</cfscript>

Once you have an instance of the JavaLoader, instantiate your cfc as usual. (In my case, I am instantiating my PdfPageEventHander.cfc) The last step is to wrap the cfc in a proxy, which was incredibly simple. First you create an array of all of the interfaces, your cfc implements. Then grab a reference to the new Dynamic Proxy class and use its createInstance method to create your proxy object. That is all there is to it.


<cfscript>
//....
//intialize the page event handler component
eventHandler = createObject("component", "PdfPageEventHandler").init( font=textFont, fontSize=10, textColor=textColor);
//add a custom footer
eventHandler.setFooterText( "www.clueless.corp * 85 anywhere blvd * lost city" );

//we can pass in an array of strings which name all the interfaces we want out dynamic proxy to implement
interfaces = ["com.lowagie.text.pdf.PdfPageEvent"];

//get a reference to the dynamic proxy class
CFCDynamicProxy = loader.create("com.compoundtheory.coldfusion.cfc.CFCDynamicProxy");

// create a proxy that we will pass to the iText writer
eventHandlerProxy = CFCDynamicProxy.createInstance(eventHandler, interfaces);
</cfscript>

Now for the real test. I passed my proxy object into my iText writer object, just as I would if it were a native java class:
// ...
<cfscript>
fullPathToOutputFile = ExpandPath("./ABetterPdfPageEventHandler.pdf");
document = loader.create("com.lowagie.text.Document").init();
outStream = createObject("java", "java.io.FileOutputStream").init(fullPathToOutputFile);
writer = loader.create("com.lowagie.text.pdf.PdfWriter").getInstance(document, outStream);

// ** register the PROXY as the page event handler **
writer.setPageEvent( eventHandlerProxy );

document.open();
Paragraph = loader.create("com.lowagie.text.Paragraph");
document.add( Paragraph.init("Paragraph Text....") );
document.newPage();
document.add( Paragraph.init("Paragraph Text....") );
document.newPage();
document.close();
outStream.close();

WriteOutput("Done!");
</cfscript>


.. and iText was none the wiser! Voila, instant footers.



A few simple changes to my cfc and I had instant headers instead. It feels like writing java code ... except in CF ;)



You know when I woke up this morning, I thought all of this might have been just a dream. But CFCDynamicProxy is real .. and this class seriously rocks!


...Read More

Saturday, June 27, 2009

CFPDF: Problems with addWatermark foreground="false"

Another interesting issue with cfpdf and watermarks came up on the adobe forums last week. A poster mentioned having problems using cfpdf to apply a watermark to the background of a pdf. Whenever they tried using foreground="false" a white rectangle always obscured the watermark.


<cfpdf action="addwatermark"
image="myWatermarkImage.gif"
foreground="false"
source="test.pdf"
destination="test_Watermarked.pdf"
overwrite="yes">

I ran a few tests and suprisingly my attempts to apply the background watermark using ddx and iText both failed. But they did reveal something strange: the problem only seems to apply to pdf's created with cfdocument. The same code worked with similar files created by Acrobat. So it definitely seems to be an issue with cfdocument.


However, a post on houseoffusion.com, by Randi Knutson, mentions a work-around using css. He was able to apply a background watermark using the css background-image property. So at least there is one way around this particular issue. For those that like one-stop-shopping, here is a quick example using Randi's code:


<cfdocument format="pdf" filename="simulateForegroundEqualsFalse.pdf" overwrite="true">
<style>
body { background-image: url(/images/myWatermarkLetterSize.gif);
</style>
<body>
<cfloop from="1" to="30" index="r">
<p>The only way to comprehend what mathematicians mean by Infinity is to contemplate the extent of human stupidity.</p>
</cfloop>
</body>
</cfdocument>




Update: A helpful Adobe rep. pointed out a simpler fix that works with the CF9 Beta. When creating the pdf with cfdocument, simply save the results to a variable. Then use the variable as the pdf "source" instead of a file path.

...Read More

CFPDF - Issues When Using Transparent Images as a Watermark

I saw an interesting question on the abode forums yesterday, about problems with watermarks and cfpdf. The issue involved using transparent png's or gif's as a watermark. The transparent parts of the image seem to be rendered as white, instead of maintaining their transparency.


As I was curious, I tried a number of different things but nothing seemed to work except a bit of iText magic. The work-around comes from an adaptation of two great iText examples. The code is very simple. It uses PdfGState to set the watermark to 50% opacity, but you can change that (and other properties like blendMode) as well.

If anyone knows a way around this issue (using cfpdf or ddx), I would love to hear it.


Update July 13,2009: This issue appears to be fixed in CF9 beta.


iText Example Java Source:



<!---
Add a centered watermark with 50% opacity
--->
<cfscript>
savedErrorMessage = "";

fullPathToInputFile = ExpandPath("mySourceFile.pdf");
fullPathToWatermark = ExpandPath("myTransparentImage.png");
fullPathToOutputFile =  ExpandPath("mySourceFile_Watermarked.pdf");

try {
    // create PdfReader instance to read in source pdf
    pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader").init(fullPathToInputFile);
    totalPages = pdfReader.getNumberOfPages();

    // create PdfStamper instance to create new watermarked file
    outStream = createObject("java", "java.io.FileOutputStream").init(fullPathToOutputFile);
    pdfStamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init(pdfReader, outStream);

    // Read in the watermark image
    img = createObject("java", "com.lowagie.text.Image").getInstance(fullPathToWatermark);

    // Use PdfGState to change fill,blendMode, etcetera as needed
    gState = createObject("java", "com.lowagie.text.pdf.PdfGState").init();
    gState.setFillOpacity(0.5);

    // adding content to each page
    p = 0;
    while (p LT totalPages) {
        p = p + 1;
        // Prepare to place image on OVERcontent
        content = pdfStamper.getOverContent( javacast("int", p) );
        // Only needed if you are changing the opacity, blending, etcetera ..
        content.setGState(gState);

        // Center the watermark. Note - using deprecated methods for CF8/iText 1.4 compatability
        rectangle = pdfStamper.getReader().getPageSizeWithRotation( javacast("int", p) );
        x = rectangle.left() + (rectangle.width() - img.plainWidth()) / 2;
        y = rectangle.bottom() + (rectangle.height() - img.plainHeight()) / 2;
        img.setAbsolutePosition(x, y);

        content.addImage(img);
        WriteOutput("Watermarked page "& p &"<hr>");
    }

    WriteOutput("Finished!");
}
catch (java.lang.Exception e) {
    savedErrorMessage = e;
}
// closing PdfStamper will generate the new PDF file
if (IsDefined("pdfStamper")) {
    pdfStamper.close();
}
if (IsDefined("outStream")) {
    outStream.close();
}
</cfscript>

<!--- show any errors --->
<cfif len(savedErrorMessage) gt 0>
    ERROR - Unable to create document
    <cfdump var="#savedErrorMessage#">
</cfif>


...Read More

Thursday, May 7, 2009

ColdFusion: Debugging CFPDFForm Information (It's never enough)

Previously, I mentioned some gotchas with handling checkboxes and radio buttons with cfpdfform. One of the most common being, using the wrong "value". Which got me to wondering, how would you verify the values are correct or incorrect, other trial and error?

Using <cfpdform action="read" ..> only returns a field's current value. It does not return all of the possible options for fields like lists, checkboxes, etcetera. But you can use iText's AcroFields class to access more detailed information about Acrobat form fields. Using the AcroFields metadata, you could easily put together a great debugging function

There is a good basis for a function in one of the great examples on the iText site. Using it as a foundation, I whipped a quick function and voila: an array of detailed field information like field type, list options/values, page numbers, coordinates, etcetera.



But it could easily be modified to return fewer or more details, if desired. Ahh, the beauty of iText.


<!---
USAGE
--->
<cfset results = pdfFormFieldDump( ExpandPath("./register_form1.pdf") )>
<cfdump var="#results#" label="pdfFormFieldDump">

<!---
FUNCTION
--->
<cffunction name="pdfFormFieldDump" returntype="array">
<cfargument name="path" type="string" required="true">

<cfset var i = "" >
<cfset var prop = "" >
<cfset var name = "" >
<cfset var type = "" >
<cfset var reader = "" >
<cfset var positions = "" >
<cfset var fieldData = "">
<cfset var AcroFields = "" >
<cfset var formData = "" >
<cfset var fieldNames = "" >

<cfscript>
// read in the pdf file and get the form field metadata
reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( arguments.path );
AcroFields = createObject("java", "com.lowagie.text.pdf.AcroFields");
formData = reader.getAcroFields();
fieldNames = structKeyArray(formData.getFields());

// extract the properties of each field
fieldData = arrayNew(1);
for (i = 1; i lte arrayLen(fieldNames); i = i +1)
{
// get the current field
name = fieldNames[i];
type = formData.getFieldType( name );

// initialize a new structure for storing this field's properties
prop = structNew();
prop.fieldName = name;
positions = formData.getFieldPositions( name );
prop.page = positions[1];
prop.llx = positions[2];
prop.lly = positions[3];
prop.urx = positions[4];
prop.ury = positions[5];

// store the field type and properties
if ( type eq AcroFields.FIELD_TYPE_CHECKBOX )
{
prop.type = "Checkbox";
prop.states = formData.getAppearanceStates( name );
}
else if ( type eq AcroFields.FIELD_TYPE_COMBO )
{
prop.type = "Combobox";
prop.options = formData.getListOptionExport( name );
prop.values = formData.getListOptionDisplay( name );
}
else if ( type eq AcroFields.FIELD_TYPE_LIST )
{
prop.type = "List";
prop.options = formData.getListOptionExport( name );
prop.values = formData.getListOptionDisplay( name );
}
else if ( type eq AcroFields.FIELD_TYPE_NONE )
{
prop.type = "None";
prop.value = formData.getField( name );
}
else if ( type eq AcroFields.FIELD_TYPE_PUSHBUTTON )
{
prop.type = "Pushbutton";
prop.value = formData.getField( name );
}
else if ( type eq AcroFields.FIELD_TYPE_RADIOBUTTON )
{
prop.type = "Radiobutton";
prop.states = formData.getAppearanceStates( name );
}
else if ( type eq AcroFields.FIELD_TYPE_SIGNATURE )
{
prop.type = "Signature";
}
else if ( type eq AcroFields.FIELD_TYPE_TEXT )
{
prop.type = "Text";
prop.value = formData.getField( name );
}
else
{
prop.type = "Unknown";
prop.value = formData.getField( name );
}

// save the properties in the main array
arrayAppend( fieldData, prop );
}
</cfscript>

<cfreturn fieldData >
</cffunction>

...Read More

Friday, April 24, 2009

iText - Preview of things to come .. someday (RTF to PDF)

Update November 19, 2009: Unfortunately, it looks like RTF will be abandoned and moved to an incubator project.
http://www.mail-archive.com/itext-questions%40lists.sourceforge.net/msg47892.html

So I have been searching around for non-commercial tools for converting rtf/word documents to pdf. (Yes, I know some of you are chuckling as you read this). The search has been interesting, and though I have not found the magic bullet yet, I did learn about some neat tools along the way, which I may write about later.

While commericial tools are probably still the best option at this point, I did come across some promising updates on the iText site. They mentioned expanding the rtf functionality to include partial support for
  • reading rtf files
  • converting rtf to pdf format.

Of course it is still under development right now. But I am looking forward to the first official release. I played around with version 2.1.5 a bit, and surprisingly I was actually able to convert an rtf file to pdf. Now since the feature is not finished, I was pleased to get any output at all. Mind you some of the rtfs I tested worked a bit better than others. A few were a bit garbled, but I am impressed. It is definitely coming along nicely. I do not know where the developers find the time ..

Now, examples for the new RTF jars are understandably a little sparse at this point. (The code has undergone some drastic changes). But for the curious, here is the code I came up with. Keep in mind I am still learning from the api. So do not take this as a model for correct code usage ;)

Java Example

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.rtf.parser.RtfParser;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class ConvertRTFToPDF {


public static void main(String[] args) {
String inputFile = "sample.rtf";
String outputFile = "sample_converted.pdf";

// create a new document
Document document = new Document();

try {
// create a PDF writer to save the new document to disk
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(outputFile));
// open the document for modifications
document.open();

// create a new parser to load the RTF file
RtfParser parser = new RtfParser(null);
// read the rtf file into a compatible document
parser.convertRtfDocument(new FileInputStream(inputFile), document);

// save the pdf to disk
document.close();

System.out.println("Finished");

} catch (DocumentException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

}

ColdFusion
Requirements: iText 2.1.5 and JavaLoader

Instructions for installing a newer version of iText without breaking ColdFusion


<h1>Convert RTF to PDF Example</h1>
As of 04/24/2009, the feature is <b>not</b> fully implemented
in iText. (In other words, do not expect it to work perfectly at this
time)<br><hr>

<cfscript>
savedErrorMessage = "";

// get a reference to the javaLoader
javaLoader = server[application.myJavaLoaderKey];

// initliaze file paths
pathToInputFile = ExpandPath("./sample.rtf");
pathToOutputFile = ExpandPath("./sample.pdf");

// create a new document
document = javaLoader.create("com.lowagie.text.Document").init();

try {
// create a PDF writer to save the new document to disk
outStream = createObject("java", "java.io.FileOutputStream").init( pathToOutputFile );
PdfWriter = javaLoader.create("com.lowagie.text.pdf.PdfWriter");
writer = PdfWriter.getInstance( document, outStream );

// open the document for modifications first
document.open();

// create a new parser to load the RTF file
parser = javaLoader.create("com.lowagie.text.rtf.parser.RtfParser").init( javacast("null", "") );

// read the rtf file into the document
inStream = createObject("java", "java.io.FileInputStream").init( pathToInputFile );
parser.convertRtfDocument( inStream, document );

// save the converted document (ie pdf) to disk
document.close();

WriteOutput("Finished! File saved: "& pathToOutputFile);

}
catch (Exception e) {
savedErrorMessage = e;
}

// always close the streams
if ( isDefined("inStream") )
{
inStream.close();
}
if ( isDefined("outStream") )
{
outStream.close();
}
</cfscript>

<!--- show any errors --->
<cfif len(savedErrorMessage) gt 0>
Error - unable to create document
<cfdump var="#savedErrorMessage#">
</cfif>

...Read More

Monday, September 22, 2008

Experiment with Calling CFFunctions from PDFPageEvents - (The Code)

Here is the complete code from my previous entry

Detailed Instructions

Using the JavaLoader.cfc and compiling the Java class with Eclipse
(The instructions for compiling the jar should be the same. Only the java code is different)

Invoke CFFunction from PdfPageEvent Example (MX7+)

Path: {wwwroot}\dev\iText\myTestPage.cfm


<h1>Invoke CFFunction from PdfPageEvent Example (MX7+)</h1>
<cfscript>
savedErrorMessage = "";

fullPathToOutputFile = ExpandPath("./CFToPDFPageEventResult.pdf");
// get instance of javaLoader stored in the server scope
javaLoader = server[application.MyUniqueKeyForJavaLoader];
document = javaLoader.create("com.lowagie.text.Document").init();

try {
outStream = createObject("java", "java.io.FileOutputStream").init(fullPathToOutputFile);
writer = javaLoader.create("com.lowagie.text.pdf.PdfWriter").getInstance(document, outStream);

// get an instance of the CFC containing my cf event functions
eventFuncs = createObject("component", "MyPageEventFunctions").init( javaLoader=javaLoader );

// get an instance of the page event component
eventHandler = createObject("component", "PDFPageEventHandler").init( javaLoader=javaLoader );

// create an instance of the java utility class
eventUtil = eventHandler.createEventUtility();

// link the "initDocument" function to the "onOpenDocument" event
eventHandler.link( eventUtility = eventUtil,
eventName = eventUtil.ON_OPEN_DOCUMENT,
functionContext = eventFuncs.getContext(),
functionObject = eventFuncs.initDocument
);


// link the "addFooter" function to the "onEndPage" event
functionArgs.footerText = "BOREDOM ALERT! BOREDOM ALERT! Page number ";
eventHandler.link( eventUtility = eventUtil,
eventName = eventUtil.ON_END_PAGE,
functionContext = eventFuncs.getContext(),
functionObject = eventFuncs.addFooter,
functionArguments = functionArgs
);


writer.setPageEvent( eventUtil );

// step 4: open the document and add a few sample pages
phrase = javaLoader.create("com.lowagie.text.Phrase");
totalPages = 10;
document.open();
for (i = 1; i LTE totalPages; i = i + 1) {
document.add( phrase.init("The best way to be boring is to leave nothing out. ") );
document.add( phrase.init("The best way to be boring is to leave nothing out. ") );
document.add( phrase.init("The best way to be boring is to leave nothing out. ") );
if (i LT totalPages) {
document.newPage();
}
}
}
catch (Exception e) {
savedErrorMessage = e;
}

// close document and output stream objects
if ( structKeyExists(variables, "document") ) {
document.close();
}
if ( structKeyExists(variables, "outStream") ) {
outStream.close();
}

WriteOutput("Done!");
</cfscript>

<!--- show any errors --->
<cfif len(savedErrorMessage) gt 0>
Error. Unable to create file
<cfdump var="#savedErrorMessage#">
</cfif>


PDFPageEventHandler.cfc

Path: {wwwroot}\dev\iText\PDFPageEventHandler.cfc
Change the default value of as needed.

<!---
PDFPageEventHandler.cfc

@author http://cfsearching.blogspot.com/
@version 1.0, September 22, 2008
--->
<cfcomponent output="false">
<cfset variables.instance = structNew()>

<cffunction name="init" returntype="PDFPageEventHandler" access="public" output="false">
<cfargument name="javaLoader" type="any" required="true" hint="Instance of the javaLoader.cfc">
<cfargument name="utilityClass" type="string" default="itextutil.CFPDFPageEvent" hint="Dot notation path for the java utility class">

<cfset variables.instance.javaLoader = arguments.javaLoader>
<cfset variables.instance.utilityClass = arguments.utilityClass>

<cfreturn this>
</cffunction>

<cffunction name="getJavaLoader" returntype="any" access="private" output="false">
<cfreturn variables.instance.javaLoader>
</cffunction>

<cffunction name="getUtilityClass" returntype="any" access="private" output="false">
<cfreturn variables.instance.utilityClass>
</cffunction>

<cffunction name="createEventUtility" returntype="any" access="public" output="false" hint="Returns a new instance of the java utility class">
<!--- create an instance of the java utility class --->
<cfreturn getJavaLoader().create( getUtilityClass() ).init() >
</cffunction>

<cffunction name="link" returntype="void" access="public" output="false" hint="Links a CFFunction to a PDFPageEvent">
<cfargument name="eventUtility" type="any" required="true" hint="Instance of the java utility class">
<cfargument name="eventName" type="string" required="true" hint="Name of the desired PDFPageEvent">
<cfargument name="functionContext" type="any" required="true" hint="Page context for the CFFunction. ie GetPageContext()">
<cfargument name="functionObject" type="any" required="true" hint="Instance of the desired CFFunction">
<cfargument name="functionArguments" type="struct" default="#structNew()#" hint="Any arguments to pass to the CFFunction">

<cfset var Local = structNew()>

<!--- the method name used to call the CF function internally --->
<cfset Local.internalMethod = "invoke">

<!--- define paramter types required to call the CF function internally --->
<cfset Local.Class = createObject("java", "java.lang.Class")>
<cfset Local.paramTypes = arrayNew(1)>
<cfset Local.paramTypes[1] = Local.Class.forName("java.lang.Object")>
<cfset Local.paramTypes[2] = Local.Class.forName("java.lang.String")>
<cfset Local.paramTypes[3] = Local.Class.forName("java.lang.Object")>
<cfset Local.paramTypes[4] = Local.Class.forName("java.util.Map")>

<!--- define arguments required to call the CF function internally --->
<!--- [1] instance, [2] function name, [3] parent, [4] function arguments --->
<cfset Local.methodArgs = arrayNew(1)>
<cfset Local.methodArgs[1] = arguments.functionContext.getFusionContext()>
<cfset Local.methodArgs[2] = getMetaData(arguments.functionObject).name>
<cfset Local.methodArgs[3] = arguments.functionContext.getPage()>
<cfset Local.methodArgs[4] = arguments.functionArguments >

<!--- using the java class, link the CF function to the specified page event --->
<cfset arguments.eventUtility.link ( arguments.eventName,
arguments.functionObject,
arguments.functionArguments,
Local.internalMethod,
Local.paramTypes,
Local.methodArgs
)>
</cffunction>

</cfcomponent>


MyPageEventFunctions.cfc

Path: {wwwroot}\dev\iText\MyPageEventFunctions.cfc



<cfcomponent output="false">
<cfset variables.instance = structNew()>

<cffunction name="init" returntype="MyPageEventFunctions" access="public" output="false">
<cfargument name="javaLoader" type="any" required="true">

<cfset variables.instance.javaLoader = arguments.javaLoader>
<cfreturn this>
</cffunction>

<!--- this is required for event handler --->
<cffunction name="getContext" returntype="any" access="public" output="false">
<cfreturn getPageContext()>
</cffunction>

<cffunction name="getJavaLoader" returntype="any" access="private" output="false">
<cfreturn variables.instance.javaLoader>
</cffunction>

<cffunction name="initDocument" returntype="void" access="public" output="false">
<cfargument name="CF_PDF_EVENT" type="struct">
<cfset var Local = structNew()>
<cfscript>
// create a font object to use for the page footer text
Local.BaseFont = getJavaLoader().create("com.lowagie.text.pdf.BaseFont");
Local.textFont = Local.BaseFont.createFont( Local.BaseFont.HELVETICA,
Local.BaseFont.WINANSI,
Local.BaseFont.EMBEDDED
);
// store the font in the event handler object
arguments.CF_PDF_EVENT.EVENT_PARENT.setProp("textFont", Local.textFont);
</cfscript>
</cffunction>

<cffunction name="addFooter" returntype="void" access="public" output="false">
<cfargument name="footerText" type="string">
<cfargument name="CF_PDF_EVENT" type="struct">
<cfset var Local = structNew()>

<cfscript>
Local.writer = arguments.CF_PDF_EVENT.EVENT_WRITER;
Local.document = arguments.CF_PDF_EVENT.EVENT_DOCUMENT;

Local.textSize = 12;
Local.Color = createObject("java", "java.awt.Color");
Local.textColor = Local.color.decode("##cc0000");
// TEST: change the text color to blue
//Local.textColor = Local.color.decode("##0000ff");

// retrieve the textFont from the event handler object
Local.textFont = arguments.CF_PDF_EVENT.EVENT_PARENT.getProp("textFont");

Local.cb = Local.writer.getDirectContent();
Local.cb.saveState();

Local.cb.beginText();
Local.cb.setColorFill(Local.textColor);
Local.cb.setFontAndSize( Local.textFont, Local.textSize);
Local.cb.setTextMatrix( Local.document.left(), Local.document.bottom() - 10);
Local.text = arguments.footerText & Local.writer.getPageNumber();
// TEST: change the footer text
//Local.text = "www.cluelesscorp.com - What page is this? ["& Local.writer.getPageNumber() &"]";
Local.cb.showText( Local.text );
Local.cb.endText();
Local.cb.restoreState();
</cfscript>
</cffunction>

</cfcomponent>



Java Utility Class Code

/**
* Generic helper class used to invoke a ColdFusion function
* from java when a PDFPageEvent occurs
*
* @author http://cfsearching.blogspot.com
* @version 1.0
*/
package itextutil;

import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Map;
import com.lowagie.text.Document;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfWriter;

public class CFPDFPageEvent extends PdfPageEventHelper {
public static final double version = 1.0;

// Keys representing the different pdf page events
public static final String ON_END_PAGE = "onEndPage";
public static final String ON_START_PAGE = "onStartPage";
public static final String ON_CLOSE_DOCUMENT = "onCloseDocument";
public static final String ON_OPEN_DOCUMENT = "onOpenDocument";
public static final String ON_CHAPTER = "onChapter";
public static final String ON_CHAPTER_END = "onChapterEnd";
public static final String ON_GENERIC_TAG = "onGenericTag";
public static final String ON_PARAGRAPH = "onParagraph";
public static final String ON_PARAGRAPH_END = "onParagraphEnd";
public static final String ON_SECTION = "onSection";
public static final String ON_SECTION_END = "onSectionEnd";


public static final String CF_FUNCTION_CLASS = "CF_FUNCTION_CLASS";
public static final String CF_METHOD_NAME = "CF_METHOD_NAME";
public static final String CF_METHOD_PARAMTER_TYPES = "CF_METHOD_NAME";
public static final String CF_METHOD_ARGUMENTS = "CF_METHOD_ARGUMENTS";

// Key for event data passed to the CF functions
public static final String CF_PDF_EVENT = "CF_PDF_EVENT";

// Key for event values passed to the CF functions
public static final String EVENT_PARENT = "EVENT_PARENT";
public static final String EVENT_WRITER = "EVENT_WRITER";
public static final String EVENT_DOCUMENT = "EVENT_DOCUMENT";
public static final String EVENT_POSITION = "EVENT_POSITION";
public static final String EVENT_TITLE = "EVENT_TITLE";
public static final String EVENT_DEPTH = "EVENT_DEPTH";
public static final String EVENT_TEXT = "EVENT_TEXT";
public static final String EVENT_RECTANGLE = "EVENT_RECTANGLE";


private Map props;
private Map eventMap;

public CFPDFPageEvent(){
super();
this.props = new Hashtable();
this.eventMap = new Hashtable();
}


public void onOpenDocument(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_OPEN_DOCUMENT);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onCloseDocument(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_CLOSE_DOCUMENT);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onStartPage(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_START_PAGE);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onEndPage(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_END_PAGE);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onChapter(PdfWriter writer, Document document, float position, Paragraph title) {
EventFunction cfData = getEventFunction(ON_CHAPTER);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
event.put(EVENT_TITLE, title);
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onChapterEnd(PdfWriter writer, Document document, float position) {
EventFunction cfData = getEventFunction(ON_CHAPTER_END);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
EventFunction cfData = getEventFunction(ON_GENERIC_TAG);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_RECTANGLE, rect);
event.put(EVENT_TEXT, text);
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onParagraph(PdfWriter writer, Document document, float position) {
EventFunction cfData = getEventFunction(ON_PARAGRAPH);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}
public void onParagraphEnd(PdfWriter writer, Document document, float position) {
EventFunction cfData = getEventFunction(ON_PARAGRAPH_END);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onSection(PdfWriter writer, Document document, float position, int depth, Paragraph title) {
EventFunction cfData = getEventFunction(ON_SECTION);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
event.put(EVENT_DEPTH, String.valueOf(depth));
event.put(EVENT_TITLE, title);
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

public void onSectionEnd(PdfWriter writer, Document document, float position) {
EventFunction cfData = getEventFunction(ON_SECTION_END);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}

protected void invokeCFFunction(EventFunction cfData) {
try {
// Get the CF function's java class
Class cfClass = cfData.getFunction().getClass();

// Locate the internal method used to invoke the function
Method cfMethod = cfClass.getMethod( cfData.getMethodName(), cfData.getParamTypes() );

// Finally, invoke call the CF function
cfMethod.invoke( cfData.getFunction(), cfData.getMethodArgs());

}
catch (Exception e) {
// convert checked exception into an unchecked exception.
throw new ExceptionConverter( new CFPDFFunctionException(e) );
}
}

protected Map createEvent() {
Map event = new Hashtable();
event.put(EVENT_PARENT, this);
return event;
}


public void link(String eventName, Object cfFuncObj, Map cfFuncArgs,
String methodName, Class[] paramTypes, Object[] methodArgs) {
// create a new function object
EventFunction func = new EventFunction(cfFuncObj, cfFuncArgs, methodName, paramTypes, methodArgs);
// link the function to the specified event
getEventMap().put(eventName, func);
}

public Object getProp(String key) {
return (this.props.containsKey(key) ? this.props.get(key) : "");
}

public void setProp(String key, Object value) {
this.props.put(key, value);
}

protected Map getEventMap() {
return this.eventMap;
}
protected EventFunction getEventFunction(String eventName) {
return (EventFunction)getEventMap().get(eventName);
}

public static void main(String[] args) {
}

/**
* Custom exception designed to make it easier to detect and catch errors from CF
*/
class CFPDFFunctionException extends Exception {
public CFPDFFunctionException(Exception e) {
super(e);
}
}

/**
* This class represents the information required to call a CF Function
*/
class EventFunction {
private Object funcInstance;
private Map funcArgs;
private String methodName;
private Class[] paramTypes;
private Object[] methodArgs;

public EventFunction( Object funcInstance, Map funcArgs, String methodName,
Class[] paramTypes, Object[] methodArgs) {

this.funcInstance = funcInstance;
this.funcArgs = funcArgs;
this.methodName = methodName;
this.paramTypes = paramTypes;
this.methodArgs = methodArgs;
}

public Object getFunction() {
return this.funcInstance;
}
public Map getFunctionArgs() {
return this.funcArgs;
}
public void addFunctionArg(Object key, Object value) {
this.funcArgs.put(key, value);
}
public String getMethodName() {
return this.methodName;
}
public Class[] getParamTypes() {
return this.paramTypes;
}
public Object[] getMethodArgs() {
return this.methodArgs;
}
}
}

...Read More

Experiment with Calling CFFunctions from PDFPageEvents - Part 2

In Part 1 I covered the basics of my java and cfc utilities. In Part 2 I will show an example of them in action. I thought it would be interesting to contrast the differences between the old and new technique. So I used a modified version of the example from Using iText's PdfPageEventHelper with ColdFusion to test the code. (If you have not read the previous entry already, it is worth a quick read. If only so the descriptions below will make more sense ;).

Anyway, my first step was converting the java methods into a CFC with two functions: one that will be called onOpenDocument and the other onEndPage. Both functions have an argument called CF_PDF_EVENT. It is a structure of information that is passed in automatically from the java utility. It contains details about the PDFPageEvent that occurred (writer, document, paragraph position, etcetera).


<cffunction name="initDocument" returntype="void" access="public" output="false">
<cfargument name="CF_PDF_EVENT" type="struct">
...
</cffunction>

Since different page events generate different information, the structure contents vary depending on which event occurred. However, the structure always contains a key named EVENT_PARENT. The value is just a reference to the java utility object. It comes in handy when you need to make properties available to other event functions. Just use the setProp() method to add a property, and getProp() to retrieve it.

For example, the initDocument function below adds a property called "textFont". This property is later used by the addFooter function when generating the footer text.

    <cffunction name="initDocument" returntype="void" access="public" output="false">
<cfargument name="CF_PDF_EVENT" type="struct">
<cfset var Local = structNew()>
<cfscript>
// create a font object to use for the page footer text
Local.BaseFont = getJavaLoader().create("com.lowagie.text.pdf.BaseFont");
Local.textFont = Local.BaseFont.createFont( Local.BaseFont.COURIER_BOLD,
Local.BaseFont.WINANSI,
Local.BaseFont.EMBEDDED
);
// store the font in the event handler object
arguments.CF_PDF_EVENT.EVENT_PARENT.setProp("textFont", Local.textFont);
</cfscript>
</cffunction>

<cffunction name="addFooter" returntype="void" access="public" output="false">
<cfargument name="footerText" type="string">
<cfargument name="CF_PDF_EVENT" type="struct">
<cfset var Local = structNew()>

<cfscript>
...
// retrieve the textFont from the event handler object
Local.textFont = arguments.CF_PDF_EVENT.EVENT_PARENT.getProp("textFont");
...
</cfscript>
</cffunction>

All that remains ...


The final step was to modify the original CF example which generated a pdf with page footers. After instantiating the writer, I create a few instances of my components. Both components use the JavaLoader.cfc, so I am passing in an instance as a parameter.


<cfscript>
...

// get an instance of the CFC containing my cf event functions
eventFuncs = createObject("component", "MyPageEventFunctions").init( javaLoader=javaLoader );

// get an instance of the page event component
eventHandler = createObject("component", "PDFPageEventHandler").init( javaLoader=javaLoader );
<cfscript>


Next I create an instance of the java utility and link my two functions to the onOpenDocument and onEndPage events. Finally, I register the utility object with the PDFWriter. The rest of the code is unchanged.


<cfscript>
...
// create an instance of the java utility class
eventUtil = eventHandler.createEventUtility();

// link the "initDocument" function to the "onOpenDocument" event
eventHandler.link( eventUtility = eventUtil,
eventName = eventUtil.ON_OPEN_DOCUMENT,
functionContext = eventFuncs.getContext(),
functionObject = eventFuncs.initDocument
);


// link the "addFooter" function to the "onEndPage" event
functionArgs.footerText = "BOREDOM ALERT! BOREDOM ALERT! Page number ";
eventHandler.link( eventUtility = eventUtil,
eventName = eventUtil.ON_END_PAGE,
functionContext = eventFuncs.getContext(),
functionObject = eventFuncs.addFooter,
functionArguments = functionArgs
);

// finally, register the page event with the pdfWriter
writer.setPageEvent( eventUtil );

...
<cfscript>

It is worth noting that most of the parameters are objects, not strings. So for instance I am passing in a function object, not a name. I think the parameters are pretty self-explanatory, with the possible exception of "functionContext". In short it accepts the results of the getPageContext() function. When the function is called from java, we need to to provide CF with the context for executing the function. So the link() function extracts the required information from getPageContext() and passes it to the java class. Since I could not find a direct way to access the context from outside a cfc, I added a small helper method to my cfc that returns the context object.

<!--- this is required for event handler --->
<cffunction name="getContext" returntype="any" access="public" output="false">
<cfreturn getPageContext()>
</cffunction>


Are we there yet??

The final test was to see if all this dynamic stuff actually worked. So I ran the updated code and it produced the same silly pink footers as in the original example.



Now with just a slight change to my CF code, I was able to generate an entirely different footer. I could have also hooked into other events on-the-fly. No recompile of the java class required. Dynamic page events. Pretty cool stuff.




Wrap up ....

There are several ways I could have designed the PDFPageEventHandler.cfc. But I did not want to place a lot of restrictions on how it could be used. So the cfc could be streamlined or tweaked to suit your own needs. Now keep in mind the code is barely tested, so feel free to play around with it. If you have any comments or suggestions on how to improve it I would love to hear them.

A big thank you to Murray for coming up with such an interesting idea!

...Read More

Sunday, September 21, 2008

Experiment with Calling CFFunctions from PDFPageEvents - Part 1



In a previous entry I wrote about using iText's PDFPageEventHelper to perform tasks like adding headers and footers to a PDF. Recently, a reader named Murray raised an interesting question:
    .. It occurs to me that an improvement would be if you could get the onPageEnd listener in the java to call a CF function that you plugged in, and then do all the end page stuff in CF rather than java. For example, you could define what your header and footer looks like dynamically and on a pdf by pdf case instead of having to recompile the java whenever you wanted a different type of header / footer that wasnt covered by the style data you pass in.

Since CF functions are java classes internally, I suspected it was possible. Of course the obvious disadvantage is that it requires venturing into "undocumented" territory, and all the risks that entails. While I am normally not a fan of that, the idea was so interesting I decided to test the theory. Just to find out if it was even possible.

How do I call thee. Let me count the ways
In order to invoke the function from java I needed to figure out what method to call. Fortunately the function class contains a well named "invoke" method, which is what I ended up using.



Unfortunately, the parameter types are very high level (java.lang.Object). So I had to guess which four (4) values should be passed to the invoke method. After poking around Eclipse a bit, I came up with what seemed to be a reasonable guess.
  1. java.lang.Object instance - getPageContext().getFusionContext()
  2. java.lang.String calledName - function name
  3. java.lang.Object parent - getPageContext().getPage()
  4. java.util.Map namedArgs - structure of argument to pass to the function
Mirror, mirror on the wall ..
I decided to use reflection to call the CF functions from my java class. If you are not familiar with it, think of it as a way to execute java code dynamically. One reason I chose reflection is that it is flexible. Plus I thought it was more in line with the goal: create a generic java shell and do all the coding and changes from ColdFusion.

Java Code Overview
There are two parts the code: a java utility class and a CFC. The java class is used to receive PDFPageEvents and essentially re-route them to a CF function. So whenever a page event occurs, the java class will invoke whatever CF function you mapped to that event. If there is no function defined for that event, the event is ignored.

Using the java class is a lot simpler than it looks. Just instantiate it and call the link() method to associate a CF function with a particular page event. All the link() method does is stores the information needed to invoke the CF function in an Object. Then it associates the function with an event, by storing the information in a structure, using the event name as the key.


public void link(String eventName, Object cfFuncObj, Map cfFuncArgs,
String methodName, Class[] paramTypes, Object[] methodArgs) {
// create a new function object
EventFunction func = new EventFunction(cfFuncObj, cfFuncArgs, methodName, paramTypes, methodArgs);
// link the function to the specified event
getEventMap().put(eventName, func);
}


Then whenever an event occurs (like onDocumentOpen), the class checks the structure to see if there is function defined for that event. If there is, it saves information about the event to the function arguments. Then calls the CF function.


public void onOpenDocument(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_OPEN_DOCUMENT);
// If there is a CF function mapped to this event
if (cfData != null) {

// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);

invokeCFFunction(cfData);
}
}


The method used to call the CF function is quite simple. Nothing very special about it, other than it uses java reflection.


protected void invokeCFFunction(EventFunction cfData) {
try {
// Get the CF function's java class
Class cfClass = cfData.getFunction().getClass();

// Locate the internal method used to invoke the function
Method cfMethod = cfClass.getMethod( cfData.getMethodName(), cfData.getParamTypes() );

// Finally, invoke call the CF function
cfMethod.invoke( cfData.getFunction(), cfData.getMethodArgs());

}
catch (Exception e) {
// convert checked exception into an unchecked exception.
throw new ExceptionConverter( new CFPDFFunctionException(e) );
}
}



CFC Code Overview
The CFC is just a small wrapper used to simplify the task of using the java utility. It contains two main functions: createEventUtility() and link(). The createEventUtility() function just creates an instance of the java class. The second function just converts the arguments into a more acceptable format and passes them along to the utility's link() function.


<cffunction name="createEventUtility" returntype="any" access="public" output="false" hint="Returns a new instance of the java utility class">
<!--- create an instance of the java utility class --->
<cfreturn getJavaLoader().create( getUtilityClass() ).init() >
</cffunction>

<cffunction name="link" returntype="void" access="public" output="false" hint="Links a CFFunction to a PDFPageEvent">
<cfargument name="eventUtility" type="any" required="true" hint="Instance of the java utility class">
<cfargument name="eventName" type="string" required="true" hint="Name of the desired PDFPageEvent">
<cfargument name="functionContext" type="any" required="true" hint="Page context for the CFFunction. ie GetPageContext()">
<cfargument name="functionObject" type="any" required="true" hint="Instance of the desired CFFunction">
<cfargument name="functionArguments" type="struct" default="#structNew()#" hint="Any arguments to pass to the CFFunction">

<cfset var Local = structNew()>

<!--- the method name used to call the CF function internally --->
<cfset Local.internalMethod = "invoke">

<!--- define paramter types required to call the CF function internally --->
<cfset Local.Class = createObject("java", "java.lang.Class")>
<cfset Local.paramTypes = arrayNew(1)>
<cfset Local.paramTypes[1] = Local.Class.forName("java.lang.Object")>
<cfset Local.paramTypes[2] = Local.Class.forName("java.lang.String")>
<cfset Local.paramTypes[3] = Local.Class.forName("java.lang.Object")>
<cfset Local.paramTypes[4] = Local.Class.forName("java.util.Map")>

<!--- define arguments required to call the CF function internally --->
<!--- [1] instance, [2] function name, [3] parent, [4] function arguments --->
<cfset Local.methodArgs = arrayNew(1)>
<cfset Local.methodArgs[1] = arguments.functionContext.getFusionContext()>
<cfset Local.methodArgs[2] = getMetaData(arguments.functionObject).name>
<cfset Local.methodArgs[3] = arguments.functionContext.getPage()>
<cfset Local.methodArgs[4] = arguments.functionArguments >

<!--- using the java class, link the CF function to the specified page event --->
<cfset arguments.eventUtility.link ( arguments.eventName,
arguments.functionObject,
arguments.functionArguments,
Local.internalMethod,
Local.paramTypes,
Local.methodArgs
)>
</cffunction>



Coming up in Part 2 - Putting it all Together.

...Read More

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep