Monday, February 15, 2010

ColdFusion 9: CFPDF - Adding Headers and Footers (and a Few Quirks) - Part 2

In Part 1 I posted some examples of text and image headers. CF9 also introduced some new constants for working with page numbers and labels.


Page Numbers
Though "Page X of Y" headers/footers are not exactly earth-shattering, it is a little easier to add them to existing pdf's with the new constants _PAGENUMBER and _LASTPAGENUMBER.

<!--- Sample Document --->
<cfdocument format="pdf" name="pdfInput">
    <cfloop from="1" to="10" index="x">
        The Blank Page 
        <cfif x lt 10>
            <cfdocumentitem type="pagebreak" />
        </cfif>
    </cfloop>
</cfdocument>

<!--- Add Page X of Y Footers --->
<cfpdf action="addFooter"
   source="pdfInput"
   name="pdfOutput"
   text="Page _PAGENUMBER of _LASTPAGENUMBER" 
/>

<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />

NumberFormat
With the help of the neat numberFormat attribute, you can change boring decimal page numbers into Roman numeral format (both upper and lower case). The supported values are: LOWERCASEROMAN, NUMERIC, UPPERCASEROMAN.

<!--- Sample Document --->
<cfdocument format="pdf" name="pdfInput">
    <cfloop from="1" to="10" index="x">
        The Blank Page 
        <cfif x lt 10>
            <cfdocumentitem type="pagebreak" />
        </cfif>
    </cfloop>
</cfdocument>

<!--- Add Page X of Y in Roman Numerals --->
<cfpdf action="addFooter"
    source="pdfInput"
    name="pdfOutput"
    numberFormat="UpperCaseRoman"
    text="Page _PAGENUMBER of _LASTPAGENUMBER" 
/>

<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />

Page Labels
If your pdf contains custom Page Labels, you can also add that text to headers/footers using the _PAGELABEL and/or _LASTPAGELABEL constants. Obviously if your pdf doesn't have custom page labels, you will probably just see the default decimal page numbers instead. But I am pretty sure you can add custom labels with ddx (or alternately with iText).


<!--- Add Page Label to Header --->
<cfpdf action="addHeader"
    source="testDocumentWithLabels.pdf"
    name="pdfOutput"
    text="<b>Label For Page (_PAGENUMBER):</b> _PAGELABEL" 
/>

<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />

Things You Cannot Do ...
Perhaps this is an obvious point, but these settings have no affect on information already embedded within the pdf. In other words, you cannot change existing pdfs with page numbers already embedded in the file. However, if you use cfpdf to generate the headers/footers you can certainly remove them and add new ones.

Printing Headers/Footers
For cases when you do not always want headers/footers to be printed, you can use the showOnPrint option to control visibility when printing. For example, you could tweak some of the previous snippets to display both the graphic and text headers on screen, but use showOnPrint="false" to make only the text header "printable". Obviously you can also use the pages attribute to apply that behavior only to select pages.

<!--- 
    Display the graphics header only on screen
--->
<cfpdf action="addHeader" 
    source="inputFile.pdf" 
    destination="pdfWithImageHeader.pdf" 
    topMargin="2.6"
    leftMargin="0"
    rightMargin="0"
    align="center"
    image="http://www.google.com/logos/olympics10-prsskating-hp.png"
    overwrite="true"
    showonprint = "false"
/>

....

<!--- 
    Display the text header both on screen and when printed
--->
<cfpdf action="addHeader" 
    source="pdfWithImageHeader.pdf" 
    name="pdfOutput" 
    topMargin="1.7"
    leftMargin="0"
    rightMargin="0"
    align="center"
    text="#headerText#"
    showonprint = "true"
/>

As you can see, it is not extremely complex. But hopefully these examples make a good addendum to the limited documentation on the addHeader / addFooters features.

...Read More

ColdFusion 9: CFPDF - Adding Headers and Footers (and a Few Quirks) - Part 1

With the explosion of new features in CFPDF, there is a lot to cover in the documentation. So it is understandably a little lacking on extra examples that go beyond the very basics. While belatedly exploring some of the extended features, I ran into a few quirks with addHeader / addFooter. Probably not news to some of you. But I thought I would share my examples and results in case it saves someone else a little time.


Text Headers and Footers
The basic concept of adding a textual headers and footers was simple enough. Just use the appropriate action, addHeader or addFooter, along with a simple text value.  Setting the text alignment is equally simple, just provide an align value (left,center, right) with the desired margins.  It is worth noting that if you omit the margin, CF uses its default.  While I would not swear to it, the default seems to be 1 inch. At least for letter sized pages.

Note: If there is too much text to display with the margin bounds, it may flow right off the page. So if working with a lot of text, you may need to adjust it with some html/css.

<!--- Align plain text header --->
<cfpdf  action="addHeader" 
    source="inputFile.pdf" 
    name="pdfOutput" 
    align="left"
    leftMargin="0.5"
    text="Left Aligned. One-half (0.5) inch from left side"
/>  

<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
Styling Text Headers and Footers
Applying html/css styles to the text is very similar to using a text watermark (added in CF 8.0.1). I did encounter one peculiarity with fonts. The names seem to be case sensitive. So on windows the font name "castellar" did nothing, but "Castellar" (with an uppercase "C") did produce the desired font.


<!--- 
Add Styled header 
--->
<cfsavecontent variable="headerText">
    <span style="font-size: 25pt; font-family: Castellar;">
       <b style="color: #0080ff;">Vancouver</b>
       <b style="color: #0000ff;">2010</b>
       <b style="color: #000080;">Olympics</b>
    </span>
</cfsavecontent>

<cfpdf action="addHeader" 
    source="inputFile.pdf" 
    name="pdfOutput" 
    topMargin="1"
    text="#headerText#"
/>

<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />

HTML/CSS Limitations
Not all html tags are supported, such as <div>, <a>, etcetera. I am assuming the same limitations apply to action="addWatermark". So whatever quirks one has, the others probably do as well. 

Now in case you see an error message complaining about an "Invalid Document..", check the stack trace for the real error. Most likely it will say something about either an invalid tag or syntax error in your html/css.


An error occurred during the ADDHEADER operation in the cfpdf tag.  
Error: Invalid Document C:\ColdFusion9\wwwroot\test\pdf\inputFile.pdf specified for source or directory.  
...

Stack Trace:
com.adobe.internal.pdftoolkit.core.exceptions.PDFInvalidDocumentException: Error during text formatting, internalProcessElement: InvalidInput: Illegal element tag "div".
....

Differences between Watermarks and Headers/Footers
One difference I have noticed is how text is sized. CF 8.0.1 added the width and height attributes to support text watermarks. From what I have observed, the width and height values seem to control the size of the watermark text, rather than any explicit font sizes in html/css.  Since the width and height attributes are not allowed with actions addHeader and addFooter,  that text can be controlled by css font sizes.

Text and Image Opacity
As with watermarks, you can also use the opacity attribute to change transparency of the header/footer. Simply use a value between 0 (transparent) and 10 (fully opaque). This example creates a styled footer whose text is 50% transparent. Opacity applies to both text and images.



<cfsavecontent variable="text">
    <span style="font-size: 18pt; font-family: Century Gothic;">
       <b style="color: #0080ff;">Vancouver</b>
       <b style="color: #0000ff;">2010</b>
       <b style="color: #000080;">Olympics</b>
    </span>
</cfsavecontent>

<cfpdf action="addFooter" 
    source="inputFile.pdf" 
    name="pdfOutput" 
    bottomMargin="0.5"
    text="#text#"
    align="center"
    opacity="5"
/>    

<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />

Image Headers and Footers
Using images for headers and footers is also supported. The image source can be a file path, url, CF image object, array of bytes, or a base64 string (using isBase64="true").

Figuring out image headers/footers took a little while. It is not that the syntax is particularly cryptic. But it was not really clear to me, from the documentation, what should happen in various cases, such as when using an over-sized or undersized image.

Based on my tests, if an image is too large to fit within the given margin dimensions, it is automatically re-sized. So if the margin is too small, you can end up with a skewed or squished image. I also observed some strange alignment issues in a few of my tests. Though I am not yet sure why. In any case, you may want to ensure your images are correctly sized beforehand, to avoid unexpected resizing behavior by cfpdf.

If you view the image url below, its size should be around 7.4 x 2.4 inches. To center align the image, I used the code below. The top margin is set to "2.6" (inches). Tall enough to fit the image without resizing, plus a small amount of padding at the top. Both the left and right margin are set to zero (0) so the image is centered across the entire page width.

Now you may notice that visually this image does not appear completely centered. But that is due to the layout of the underlying graphic. But you can adjust the position by changing the alignment and leftMargin.


<!---
    Sample Document
--->
<cfdocument format="pdf" fileName="inputFile.pdf" pageType="Letter" marginTop="2.6" overwrite="true">
    <style type="text/css" media="screen">
        div { font-family: Castellar; padding: 10px; color: 204a64; } 
    </style>
    <cfloop from="1" to="10" index="x">
        <div> 
            Alpine Skiing * Biathlon * BobSleigh * Cross-Country Skiing * Figure Skating * 
            Ice Hockey * Luge * Curling * Snowboard * Speed Skating
        </div>
        <cfif x lt 10>
            <cfdocumentitem type="pagebreak" />
        </cfif>
    </cfloop>
</cfdocument>

<!--- 
    Add image header (Note: Using image URL just for demonstration purposes)
--->
<cfpdf action="addHeader" 
    source="inputFile.pdf" 
    destination="pdfWithImageHeader.pdf" 
    topMargin="2.6"
    leftMargin="0"
    rightMargin="0"
    align="center"
    image="http://www.google.com/logos/olympics10-prsskating-hp.png"
    overwrite="true"
/>

Multiple Headers / Footers
It is also possible to layer headers/footers by adding text on top of an existing image. To demonstrate, read in the file created in the previous code snippet and adjust the margin size so the text is placed in the desired position. The results should look something like this



<!--- 
Add text header on top of existing image header
--->
<cfsavecontent variable="headerText">
    <span style="font-size: 25pt; font-family: Castellar;">
       <b style="color: #0080ff;">Vancouver</b>
       <b style="color: #0000ff;">2010</b>
       <b style="color: #000080;">Olympics</b>
    </span>
</cfsavecontent>

<cfpdf action="addHeader" 
    source="pdfWithImageHeader.pdf" 
    name="pdfOutput" 
    topMargin="1.7"
    leftMargin="0"
    rightMargin="0"
    align="center"
    text="#headerText#"
/>

<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />

Remove Headers/Footers
In the process of running all of these tests, I discovered something worth noting about images.  Using an image obviously increases the file size. In some cases substantially (I have not gotten around to doing any size comparisons between cfpdf and iText yet). Since adding a new header/footer does not remove any existing ones, if you mistakenly add an image twice, it will increase the file size .. twice. But you can always use the action="removeHeaderFooter" first. (Yes, it does remove both headers and footers).


<!--- 
    Remove headers and footers
--->
<cfpdf action="removeHeaderFooter" 
        source="pdfWithImageHeader.pdf" 
        name="pdfOutput" 
    />

<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />

On a side note, if you use cfdocument to generate header and footers, as opposed to the new cfpdf actions, be aware there is a "Known Issue" mentioned in the CF9 release notes.


Bug 76078:
Headers or footers added while generating PDF using cfdocument are not marked and are not recognized as headers or footers. Therefore, cfpdf action="removeheaderfooter" will not be able to remove them from source PDF.

ColdFusion 9: CFPDF - Adding Headers and Footers (and a Few Quirks) - Part 2

...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

OT: Valentine's Day Humor

A recent link in twitter pointed out a Science Valentine comic. It got me to thinking about the strange rituals and spectacle of Valentine's Day. It has always seemed like a hokey, greeting-card holiday, more overtly commercial than all the others. Of course most holidays have become increasingly commercialized. But despite all that, I still love the magic of Christmas, the mystery of Halloween, and the inevitable laughter and tears that accompany most dysfunctional family "get-togethers" at Thanksgiving. But to me, Valentine's Day only evokes a sense of curiosity and humor at the artificially induced drama of it all.


As a young children we usually find the whole concept "icky" and want nothing to do with it. As we get a little older we must endure the angst of deciding whether or not to give a Valentine's Day card to someone we "like". (But.. if I do that then they'll _know_ I like them!) The pins and needles of waiting to see if that person you like gives you a card. The elation when they do. The crushing disappointment when they do not. Not to mention the awkwardness of receiving a card from someone you do not like. (Mom, is there a nice way to say "Ewwww. Gross. No way."?) Oh the high drama of primary school.

Even as adults, the day still evokes strong emotions in many of us. Those that are in relationships tend to "coo" and "gush" to a revolting degree. Those that are not, either piteously wallow in sorrow while tossing back copious amounts of beer or scarfing down chocolates, or they turn morose and bitter making scathing quips about the doomed nature of all relationships.

Despite the ups and downs of many Valentine's Days past, I choose to view the day with a bit of my usual weird humor. It is best summed up by the following song requests I heard one year:


  • Love Stinks (J. Geils Band)
  • Used To Love Her (Guns N' Roses)
  • She Hates Me (Puddle of Mud)


Thinking of it still makes me laugh. If you have ever listened to the songs, and have a sense of humor about the day, you will know exactly why ;)

...Read More

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep