From: Lumpia on
Coders,

Please forgive me in advance as my java kung foo is not that strong. I have a
site which allows users to merge multiple pdf documents they choose via a
checklist, and then it will apply a small watermark to the top left (xposition
158, yposition 743). The problem is that there are some pdfs in which the
watermark gets set in the middle of the page instead of the top left. I
believe I have read in some forums where it may be a size issue of sorts, but
I'm still confused. I do know that I have found a weird work around, which
involves saving the pdf documents that don't work right, then rescanning via
copy machine and having my copy machine send it back to me via email. It will
then apply the watermark to the upper left corner. I will attach my code below
(note: code is written in coldfusion and java):


I hope this isn't confusing. Thanks in advance for everyone's assistance.

<!--- BEGIN WATERMARK SCRIPT --->

<cfscript>

paths = arrayNew(1);

/*
This points to the jar we want to load.
Could also load a directory of .class files
*/
paths[1] = expandPath("iText.jar");

//create the loader
loader2 = createObject("component", "javaloader.JavaLoader").init(paths);

function insertWatermarkPDF(pdfFileIn, imageFile, pdfFileOut, xPos, yPos,
zIndex)
{
// zindex refers to placing the image over or under the content, default is
under
// but if your content has a background, it may obscure the watermark
if (NOT structKeyExists(arguments, "zIndex"))
arguments.zIndex = 0;

//try
//{
document = loader2.create("com.lowagie.text.Document");
document.init();

pdfReader = loader2.create("com.lowagie.text.pdf.PdfReader");
pdfReader.init(arguments.pdfFileIn);

// page countING
n = pdfReader.getNumberOfPages();

// creae an outputstream
streamOut = loader2.create("java.io.FileOutputStream");
streamOut.init(arguments.pdfFileOut);

// give it to the pdfstamper
pdfStamper = loader2.create("com.lowagie.text.pdf.PdfStamper");
pdfStamper.init(pdfReader, streamOut);

// contentbyte is a static object, no constructor
under = loader2.create("com.lowagie.text.pdf.PdfContentByte");

// create the Image handler, static object, no constructor
Image = loader2.create("com.lowagie.text.Image");

// now create an instance with this file (report_watermark.jpg)
img = Image.getInstance(JavaCast("string", arguments.imageFile));
img.setAbsolutePosition(arguments.xPos, arguments.yPos);

for (ii = 1; ii LT n; ii = ii + 1)
{
// pdfStamper supports getUnderContent() and getOverContent()
if (arguments.zIndex)
under = pdfStamper.getOverContent(JavaCast("int", ii));
else
under = pdfStamper.getUnderContent(JavaCast("int", ii));

under.addImage(img);
}

pdfStamper.close();

return true;
//}
//catch (any e)
//{
// return false;
//}
}

</cfscript>

<cfoutput>#insertWatermarkPDF("<PATH REMOVED>\concatenation.pdf"
,"<PATH REMOVED>\n#form.tail#up.gif"
,"<PATH REMOVED>\concatenation2.pdf"
,158
,743
,1)#
</cfoutput>

From: Lumpia on
Ok, it appears that what I need to do is implement the use of iText's mediabox and cropbox. Has anyone successfully used mediabox and cropbox within a cfscript tag? Thanks.
From: -==cfSearching==- on
What issue are you having with it? Using your code as a base, this simple
example displays the cropbox information for each page.

<cfscript>
pdfReader = loader2.create("com.lowagie.text.pdf.PdfReader");
pdfReader.init(arguments.pdfFileIn);
n = pdfReader.getNumberOfPages();

for (ii = 1; ii LTE n; ii = ii + 1)
{
// display crop box for each page
box = pdfReader.getCropBox(ii);
WriteOutput(" PAGE "& ii &" (crop box): ");
WriteOutput(" Top="& box.getTop());
WriteOutput(" Right="& box.getRight());
WriteOutput(" Bottom="& box.getBottom());
WriteOutput(" Left="& box.getLeft());
WriteOutput(" Width="& box.getWidth());
WriteOutput(" Height="& box.getHeight() &"<br>");
}
</cfscript>


Having nothing to do with watermarks, I did notice a two things that might
cause other problems.

Lumpia wrote:
[i]//create the loader
loader2 = createObject("component", "javaloader.JavaLoader").init(paths);[/i]

There is a bug in ColdFusion that can cause a memory leak when using
java.net.URLClassLoader. So it is recommended that you store the javaLoader in
the server scope rather than instantiating it each time it is used.
http://www.transfer-orm.com/?action=displayPost&ID=212


[i]document = loader2.create("com.lowagie.text.Document");
streamOut.init(arguments.pdfFileOut);[/i]
...
pdfStamper.close();
[/i]

Consider structuring your try/catch code so it always closes document,
outputstream and stamper objects. Even if an error occurs. Sort of like a
try/catch/finally clause. IMO it is a good practice that can prevent locked
files and other io related problems.

From: Lumpia on
Cfsearching,

Thanks for your many advices. Regarding the memory leak issue, I am going to
incorporate that now. I figure the way to add it to server scope with UUID
would be like so:

server.uuid.loader = createObject("component",
"javaloader.JavaLoader").init(paths);

However, for each referencing object, would I also add server.uuid on them,
like so:

pdfCopy=server.uuid.loader.create("com.lowagie.text.pdf.PdfCopy");
pdfReader=server.uuid.loader.create("com.lowagie.text.pdf.PdfReader");
pageSize=server.uuid.loader.create("com.lowagie.text.PageSize").init();
bookMark=server.uuid.loader.create("com.lowagie.text.pdf.SimpleBookmark");
pdfDocument=server.uuid.loader.create("com.lowagie.text.Document");

In reference to media & crop box, I've been reading 'iText In Action' manual
and it has this example:

Document document = new Document(new Rectangle(432, 792));
PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream("page_boundaries.pdf"));
writer.setCropBoxSize(new Rectangle(5, 5, 427, 787));

which is written in java, of course, so I'm having troubles converting it to
use in cfscript and also knowing where to place it in my current code.

Thanks again for your help.

From: -==cfSearching==- on
Lumpia wrote:
[i]I figure the way to add it to server scope with UUID would be like so:
....[/i]

Almost. Just make sure the server key for the javaloader is unique. I usually
create a UUID with the CreateUUID function and hardcode that value into my
Application.cfc file. The key is used when instantiating the javaLoader into
the server scope. I then make the key avaliable to all pages by setting a
variable in the OnRequest function. Though you could also use Request
variables if you prefer. See the attached example.

[i]In reference to media & crop box, I've been reading 'iText In Action'
manual and it has this example:

Document document = new Document(new Rectangle(432, 792));
PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream("page_boundaries.pdf"));
writer.setCropBoxSize(new Rectangle(5, 5, 427, 787));
[/i]

Well, I do not think you want to set the cropBox so much as use its dimensions
to determine where to position the watermark. At least that is the impression
I am getting. Do you have an example of one of the pdfs in which the watermark
gets set to the middle of the page?

Application.cfc
<cfcomponent>
<cfset this.name = "iTextExamples">
<cfset this.Sessionmanagement = true>
<cfset this.loginstorage = "session">

<cffunction name="onApplicationStart">
<!--- do not use this key. it is not a real UUID --->
<cfset application.MyUniqueKeyForJavaLoader =
"1E66946E-D893-FQ23-3B4DEFB37472374">
<!--- if we have not already created the javaLoader --->
<cfif NOT structKeyExists(server, application.MyUniqueKeyForJavaLoader)>
<!--- construct an array containing the full path to the jars you wish to
load --->
<cfset paths = arrayNew(1)>
<cfset arrayAppend(paths, expandPath('/dev/iText/iText-2.0.7.jar'))>
<cfset arrayAppend(paths, expandPath('/dev/iText/itextUtil.jar'))>
<cflock scope="server" type="exclusive" timeout="10">
<!--- re-verify the javaloader was not already created --->
<cfif NOT StructKeyExists(server,
application.MyUniqueKeyForJavaLoader)>
<cfset server[application.MyUniqueKeyForJavaLoader] =
createObject("component", "javaloader.JavaLoader").init(paths)>
</cfif>
</cflock>
</cfif>
</cffunction>

<cffunction name="onRequest">
<cfargument name="targetPage" type="String" required=true/>
<cfset MyUniqueKeyForJavaLoader = application.MyUniqueKeyForJavaLoader >
<cfinclude template="#Arguments.targetPage#">
</cffunction>
</cfcomponent>


Then call it in my CF pages like so

<cfscript>
// get a reference to the javaLoader
javaLoader = server[MyUniqueKeyForJavaLoader];
// the use the local variable for all subsequent actions
pdfCopy= javaLoader.create("com.lowagie.text.pdf.PdfCopy");
....
</cfscript>