Thursday, December 30, 2010

Stamping PDF files

In a previous post, I examined how we can use LiveCode and the Java-based iText library to concatenate a series of existing PDF files into a single PDF file. Now we will examine how we can 'stamp' a PDF file with an image using the same technique.

The first thing to code is the Java class that we will call using the shell function. Here's what I came up with:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import com.lowagie.text.DocumentException;
import com.lowagie.text.Image;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;

public class StampPdfFile {
public static void main(String[] args) throws IOException, DocumentException {
final String inputFile = args[0];
final String outputFile = args[1];
final String imageFile = args[2];
final String[] coords = args[3].split(",");
final PdfReader inputReader = new PdfReader(inputFile);
final OutputStream outputStream = new FileOutputStream(outputFile);
final PdfStamper outputStamper = new PdfStamper(inputReader, outputStream);
final int pageCount = inputReader.getNumberOfPages();
final Image image = Image.getInstance(imageFile);
final int left = Integer.parseInt(coords[0]);
final int top = Integer.parseInt(coords[1]);
final int right = Integer.parseInt(coords[2]);
final int bottom = Integer.parseInt(coords[3]);
final int height = bottom - top;
final int width = right - left;
image.scaleToFit(width, height);
for (int pageIndex = 1; pageIndex <= pageCount; pageIndex++) {
final PdfContentByte overContent = outputStamper.getOverContent(pageIndex);
final Rectangle pageSize = inputReader.getPageSize(pageIndex);
image.setAbsolutePosition(left, pageSize.getHeight() - bottom);
overContent.addImage(image);
}
outputStamper.close();
}
}

In a nutshell, the first parameter is the input file, the second the output file, the third the image file, and the fourth parameter is a comma-separated list of coordinates making up the target rectangle. As usual, the code is a tad lazy when it comes to faulty input parameters and exception handling - if there's a mistake you'll simply get the stacktrace as the output of the shell command.

The most important bit is in the loop over the pages, where we use the outputStamper.getOverContent() method to draw our image on top of the existing content. If you'd rather have the image in the back, as a watermark, you would use the outputStamper.getUnderContent() methopd instead. Also note that setting the image position coordinate system works from the bottomLeft of the page, so we have to use the original page height and subtract the bottom coordinate from it.

Now we can proceed with writing a LiveCode button script:
on mouseUp
--> determine the input, output and image files
local tInputFile, tOutputFile, tImageFile
put ShellPath(AbsolutePathFromStack("demo1.pdf")) \
into tInputFile
put ShellPath(AbsolutePathFromStack("stamp.pdf")) \
into tOutputFile
put ShellPath(AbsolutePathFromStack("Template.png")) \
into tImageFile
--> determine the image target rectangle
local tImageRect
put quote & "10,10,103,87" & quote \
into tImageRect
--> determine the class path
local tClassPath
if the platform is "Win32" then
put ".;iText-2.1.7.jar" into tClassPath
else
put ".:iText-2.1.7.jar" into tClassPath
end if
--> assemble the shell command
local tShellCommand
put "java -classpath" && tClassPath && \
"StampPdfFile" && \
tInputFile && \
tOutputFile && \
tImageFile && \
tImageRect \
into tShellCommand
--> execute the shell command
local tHideConsoleWindows, tDefaultFolder, tShellResult
put the hideConsoleWindows into tHideConsoleWindows
set the hideConsoleWindows to true
put the defaultFolder into tDefaultFolder
set the defaultFolder to AbsolutePathFromStack()
put shell(tShellCommand) into tShellResult
set the defaultFolder to tDefaultFolder
set the hideConsoleWindows to tHideConsoleWindows
if tShellResult is not empty then
answer error tShellResult
end if
end mouseUp

function AbsolutePathFromStack pFileName
local tAbsolutePath
put the effective filename of this stack into tAbsolutePath
set the itemDelimiter to slash
if pFileName is not empty then
put pFileName into item -1 of tAbsolutePath
else
delete item -1 of tAbsolutePath
end if
return tAbsolutePath
end AbsolutePathFromStack

function ShellPath pPath
if the platform is "Win32" then
put quote & pPath & quote into pPath
else
replace space with backslash & space in pPath
end if
return pPath
end ShellPath

Click the button, and it happily takes the existing PDF files (demo1.pdf), paints the image (Template.png) on top of all pages, and writes a new PDF file (stamp.pdf) in the same folder as our stack. There we have it, another example of using iText from within LiveCode.

No comments: