Orange is my favorite color

I have a big report that I’m generating using CFDOCUMENT. It replicates a paper form by using a background image of the original form (in PNG format) and overlaying data from a database on top of it, absolutely positioned using CSS.

I have experienced a number of image scaling issues trying to use CSS background images and fixing the page width and so forth. The only way I was able to get it right was using a table <td> background image with a stated width and height in inches:

<td background="foo.png" style="height: 10.2in; width: 7.4in">blah blah blah</td>

But, the downside is that with each page using this image, it takes a really long time to generate the report as CFDOCUMENT resamples the image for each and every page to match a target DPI resolution. 100 pages was timing out with a 240 second limit.

With some pointers from Nathan Dintenfass, I culled a few code snippets and hacked out a UDF that will use the built-in iText library in ColdFusion MX 7 to insert a watermark image on every page of a PDF above or below the content:

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 = CreateObject("java", "com.lowagie.text.Document");
		document.init();

		pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader");
		pdfReader.init(arguments.pdfFileIn);

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

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

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

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

		// create the Image handler, static object, no constructor
		Image = createObject("java", "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;
	}
}

Usage looks like:

insertWatermarkPDF("c:\\jrun4\testreport.pdf"
	,"c:\\jrun4\\servers\\cfusion\\cfusion-ear\\cfusion-war\\gfx\\report_bg.png"
	,"c:\\jrun4\\testreport_marked.pdf"
	,0
	,0
	,1);

The x/y offset is from the lower left of the page. In my very basic testing, I was able to stamp a simple image (a 300×30 logo) onto 12 pages of a report in about 30ms and the full-page background (a 75kb PNG) onto 12 pages in about 300ms. If it scales linearly, which I will be testing soon, that looks like stamping 100 pages would take 3 seconds.

If you are trying to place your watermark behind your content and it’s not showing up, it’s probably because your HTML has a background color. I haven’t tested yet but a transparent background declaration in the CSS should solve that issue.

You can extend this UDF or add enhancements by browsing the iText API. If you do, please leave a comment.

11 Comments

  1. brian said:

    on August 28, 2006 at 9:56 pm

    Another problem with using background images, or images in general in cfdocument, is that the underlying engine must fetch the image in order to render it. If you check your logfiles, you will see hits like this:


    69.36.240.124 - - [28/Aug/2006:21:54:40 -0700] "GET /gfx/report_scca_bg.png HTTP/1.1" 200 76049 "-" "Mozilla/5.0 (Java 1.4.2_05; Linux 2.4.21-40.ELsmp i386; en_US) ICEbrowser/v6_1_1 Java/1.4.2_05"

    I’m running tests tonight and tomorrow… more discoveries to come.

  2. Dan G. Switzer, II said:

    on August 29, 2006 at 7:30 am

    Brian,

    I remember reading somewhere that you can have it access local files, and I believe that done by using the “file://” protocol declaration.

    -Dan

  3. brian said:

    on August 29, 2006 at 9:59 am

    Dan, you’re 100% right. I just tried this on a 110 page report and processing time was cut by about 10s (out of 140) but the load on the machine was 50% of what it was when there were 110 HTTP requests.

    In a few limited tests using the watermark UDF (and not including the image in the original report), I cut processing time from about 140s to 55s. The constant resampling of the PNG I’m using is a real killer and this single watermark step seems to get around that.

    I think I’ll write another post once I solve this and have it back in production.

  4. brian said:

    on September 7, 2006 at 10:32 am

    A couple of updates – to make this work in CFDOCUMENT, you must use triple slashes instead of double, e.g.:

    <img src="file:///web/dir/gfx/file.jpg" />
    <img src="file:///c:\some\other\path\gfx\file.jpg" />

    For me, I develop on Windows and deploy to Linux so paths need to change but the file:/// protocol can’t handle relative paths. That’s where expandPath() comes in handy:


    <img src="file:///#expandPath("../../gfx/file.jpg")#" />

    Now we can specify the path to the background image in my report relatively and it will be correct on both Windows and Unix without any code changes.

  5. amit said:

    on September 18, 2006 at 1:51 am

    bgbb

  6. Tim said:

    on September 29, 2006 at 9:29 am

    I found your code trying to add a watermark to a CFDOCUMENT-generated PDF. When I try it out, it does indeed create a new PDF, but I cant seem to get the watermark to display. The size of the old and new PDFs are the same, which makes me wonder if it is really working. I am calling insertWatermarkPDF passing in 100 as the zindex trying to force it to the top (to see if it shows up) but I still dont see it.

    The HTML in my document doesnt have any background color specified. Where am I going wrong?

    Tim

  7. brian said:

    on September 29, 2006 at 9:39 am

    @Tim, the zIndex value is either 0 (under) or 1 (over). Passing in 100 just behaves like 1 in my example. I do the same thing as you, it requires three steps:

    1. Use CFDOCUMENT and write the results to disk
    2. Use the UDF above to insert the watermark
    3. Retrieve/CFCONTENT the new PDF back to the user

    I don’t believe you can use the same filename for in and out, if that’s what you’re doing, due to file lock issues.

    Also, keep in mind that the x/y are specified from the lower left hand corner so it’s possible, depending on what values you use, that your watermark might be off the screen.

  8. Tim said:

    on September 29, 2006 at 12:06 pm

    I understand what you are saying–that is what I am doing. I create a PDF using CFDocument and save it. Then I call the UDF. I actually changed the UDF so it properly var’d the variables since I have it in a CFC. I see the original PDF, as well as the PDF the UDF creates (yes, a different name). Both have the exact same size, which is why I wonder if it is really working for me. When I open the new PDF, I dont see any image. In my call, I specify zindex of 1, x of 100 and y of 100. Then I tried y of -100 thinking that was the problem.

    Then I thought maybe the image path I am passing in isnt valid, but an image does exist at that location.

    My call (I renamed the UDF to insertImageOntoPDF:

    Do you see anything wrong?

    Tim

  9. Tim said:

    on September 29, 2006 at 12:10 pm

    Note sure why my comment above dropped the stuff in between CODE tags, but here it is again:


    insertImageOntoPDF("c:\temp\certificate.pdf","c:\sites\www\images\mylogo.png","c:\temp\certificate_wm.pdf",100,100,1);

  10. brian said:

    on September 29, 2006 at 12:16 pm

    @Tim, I just found an interesting difference between the code I posted and the way it displayed. In my CFMX on my Windows laptop, I have escaped all of the backslashes so the paths look like this:

    c:\\\\somepath\\\\file.pdf

    That should be c: (two backslashes) somepath (two backslashes) file.pdf.

    Can you try that? Otherwise… maybe send me the PDF and the PNG and I can try it here to see if there is a difference?

  11. Tim said:

    on September 29, 2006 at 12:36 pm

    The double slashes didnt seem to have an effect for me. I still get the new PDF created, just looks the same as the original PDF. I can send you the PDF and PNG. Where should I send them? My email is on this comment.

    Thanks in advance for taking a look–

    Tim

{ RSS feed for comments on this post}