Monday, June 29, 2009

That which we call a structure, by any other name does not smell as sweet

I came across an interesting question on the forums today about a quirk of the FORM scope. On the off chance you have not already encountered this one, the poster discovered that StructCount() does not return the correct value after keys are deleted using StructDelete.

To demonstrate, create a simple form with one or more fields. When the form is submitted, delete the keys one by one. Check the StructCount() value on each iteration. You will notice the resulting counts are incorrect. The size of the FORM structure always remains the same. Even after keys are deleted.


<cfif structKeyExists(FORM, "testThis")>
<cfoutput>
<cfloop list="#form.fieldNames#" index="key">
<cfset StructDelete(FORM, key)>
Deleted key #key#. New StructCount(FORM) = #StructCount(FORM)#<br>
</cfloop>
</cfoutput>
</cfif>

<cfoutput>
<h3>Test Form</h3>
<form action="#CGI.SCRIPT_NAME#" method="post">
<cfloop from="1" to="10" index="x">
<input type="text" name="field#x#" value="#x#">
</cfloop>
<input type="submit" name="testThis">
</form>
</cfoutput>
Interestingly, if you copy the form structure with duplicate(), then run the same code on the copied object, the results are correct. The reason is duplicate() returns a slightly different type of object. If you check the underlying class names, you will see that duplicate returns a coldfusion.runtime.Struct object. Whereas form is actually a coldfusion.filter.FormScope object.

<cfset copy = duplicate(FORM)>
<cfoutput>
FORM object type = #form.getClass().name#<br>
COPY object type = #copy.getClass().name#<br><br>
</cfoutput>

Out of curiosity, I used several different methods to verify the structure counts (some less than efficient). Since ColdFusion structures are java.util.Map objects, I also used the undocumented size() method to confirm the results. It too returned the wrong value. Though I do not know how StructCount() works internally, I suspect it uses size(). Which would explain the StructCount() bug.



It turns out the same problem applies to the URL scope. At least with ColdFusion 8. So while we tend to think of URL and FORM as indistinguishable from "regular" CF structures .. they are not. In this case, neither one came out smelling like a rose ;)

Complete Test Code
<cfif structKeyExists(FORM, "testThis")> 
<!--- create a deep copy of the FORM scope --->
<cfset copy = duplicate(FORM)>

<cfoutput>
<!--- Display the object types --->
<h3>Object Types:</h3>
FORM object type = #form.getClass().name#<br>
COPY object type = #copy.getClass().name#<br><br>

<table>
<tr><th rowspan="2">Action</th>
<th colspan="4" class="one">FORM Object</th>
<th colspan="4" class="two">Copy</th>
</tr>
<tr>
<td class="one">StructCount</td>
<td class="one">Size()</td>
<td class="one">ArrayLen()</td>
<td class="one">ListLen()</td>
<td class="two">StructCount</th>
<td class="two">Size()</th>
<td class="two">ArrayLen()</th>
<td class="two">ListLen()</td>
</tr>
<!--- Display the counts as each key is deleted --->
<cfloop list="#form.fieldNames#" index="key">
<cfset StructDelete(COPY, key)>
<cfset StructDelete(FORM, key)>
<tr>
<td>Deleted key #key#</td>
<td>#StructCount(FORM)#</td>
<td>#FORM.size()#</td>
<td>#ArrayLen(StructKeyArray(FORM))#</td>
<td>#ListLen(StructKeyList(FORM))#</td>
<td>#StructCount(COPY)#</td>
<td>#COPY.size()#</td>
<td>#ArrayLen(StructKeyArray(COPY))#</td>
<td>#ListLen(StructKeyList(COPY))#</td>
</tr>
</cfloop>
</table>
</cfoutput>
</cfif>


<cfoutput>
<h3>Test Form</h3>
<form action="#CGI.SCRIPT_NAME#" method="post">
<cfloop from="1" to="10" index="x">
<input type="text" name="field#x#" value="#x#">
</cfloop>
<input type="submit" name="testThis">
</form>
</cfoutput>

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

Monday, June 1, 2009

MS SQL / ALTER TABLE: Add a new column with default values

While not an every day occurence, I often need to add columns to an existing sql server table. But it is a bit of a pain if the new column should not allow nulls. Fortunatelly, sql server provides the WITH VALUES clause for just such a situation. Because I can never remember the exact syntax (when I most need it), here is an example to jog my memory next time ;)

  
--- Change "BIT" to whatever data type is needed
--- and "0" to whatever default value is desired
ALTER TABLE MyTable ADD MyNewColumName BIT NOT NULL
CONSTRAINT MyConstraintName
DEFAULT 0 WITH VALUES

...Read More

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep