Jordan Lundgren
Jordan Lundgren

Reputation: 273

Adding footer / header to PDF via onEndPage using Coldfusion and iText

I'm using using ColdFusion and iText to generate PDF documents, and I want to be able to add a header and a footer to a PDF "automatically" whenever iText decides to make a page break. I've seen examples of how to do this in Java, and here is the basic way to do that:

public class MyHeaderFooterPageEvent extends PdfPageEventHelper {
    public void onEndPage(PdfWriter writer, Document document) {
        //add your content here
    }
} 

And then, before you start composing your PDF document, you "register" your event like his:

    MyHeaderFooterPageEvent event = new HeaderFooterPageEvent();
    writer.setPageEvent(event);

(At least as far as I understands it.)

I've successfully managed to load the class PdfPageEventHelper into ColdFusion, so I know it's there:

<cfset local.PdfPageEventHelper=CreateObject("java","com.lowagie.text.pdf.PdfPageEventHelper")>

And when I dump local.PdfPageEventHelper i get:

enter image description here

So how do I tell ColdFusion what to do when the onEndPage is called, and how do I register the event so it will be called?

I'm using Coldfusion 11 on a Linux server.

Upvotes: 2

Views: 740

Answers (1)

Jordan Lundgren
Jordan Lundgren

Reputation: 273

This solution is mainly based on this old blog post at cfSearching.blogspot.se.

While that solution uses the javaloader dynamic proxy library, this solution uses the newer(?) function CreateDynamicProxy (available from ColdFusion 10 I think), which Ageax (many thanks!) made me aware of.

This has been successfully tested in ColdFusion 11 on a Linux server.

To try it yourself:

  1. Copy and paste the content below into one cfm file and one cfc file. While you can name the cfm file whatever you want, the cfc file must be named PdfPageEventHandler.cfc in order for this code to work.
  2. Put the files in the same folder in a www root on you ColdFusion server.
  3. Run the code by surfing to the cfm-file.
  4. The browser should display a PDF of 3 pages with body text and footers.

content of the cfm-file:

<!---call function--->
<cfset _createPdf()>
<!---function for creating pdf with footer that is created via an event handler in onEndPage--->
<cffunction name="_createPdf" output="no" returntype="void">
    <cfargument name="pdfFile" type="string" default="document.pdf" hint="the file name to write to disc">
    <cfargument name="writeToBrowser" type="boolean" default="true" hint="will open the pdf document in the browser and delete the file when done">
    <cfscript>
        //set filePath for document
        local.filePath = ExpandPath('.')&"/"&arguments.pdfFile;
        //get BaseFont
        local.BaseFont = CreateObject("java", "com.lowagie.text.pdf.BaseFont");
        //get CMYK color
        local.CMYKColor = CreateObject("java", "com.lowagie.text.pdf.CMYKColor");
        //create color
        local.color = local.CMYKColor.init(JavaCast("int",0),JavaCast("int",0),JavaCast("int",0),JavaCast("int",256));
        //create font
        local.font = local.BaseFont.createFont(local.BaseFont.COURIER, local.BaseFont.CP1252, true);
        //compose custom config to make accessable in PdfPageEventHandler.cfc
        local.config = {myCustomFooter = {text = "Page number", color = local.color, font = local.font, size = 20}};
        //init the event handler (make sure the PdfPageEventHandler.cfc is present in the same folder as this template)
        local.eventHandler = CreateObject("component", "PdfPageEventHandler").init(config = local.config); 
        //we can pass in an array of strings which name all the interfaces we want out dynamic proxy to implement
        local.interfaces = ["com.lowagie.text.pdf.PdfPageEvent"];
        //create a dynamic proxy that we will pass to the iText writer
        local.eventHandlerProxy = CreateDynamicProxy(local.eventHandler, local.interfaces);
        //init success flag
        local.success = true;
        try {
            //init document
            local.document = CreateObject("java", "com.lowagie.text.Document").init();
            //init outstream
            local.outputStream = CreateObject("java", "java.io.FileOutputStream").init(local.filePath);
            //init writer
            local.writer = CreateObject("java", "com.lowagie.text.pdf.PdfWriter").getInstance(local.document, local.outputStream);
            //register the PROXY as the page event handler
            local.writer.setPageEvent(local.eventHandlerProxy);
            //open document
            local.document.open();
            //init paragraph
            local.paragraph = CreateObject("java", "com.lowagie.text.Paragraph");
            //output some pages with a simple text message. NOTE that our eventHandler will take care of adding footers on every page - that's the whole point with this example by the way :)
            for (local.i = 1; local.i lte 3; local.i = local.i + 1) {
                //add paragraph with text
                local.document.add(local.paragraph.init("This page should have a footer with a page number on it!"));
                //trigger new page
                local.document.newPage();
            }
        } catch (any e) {
            //an error occured
            WriteOutput("Error: " & e.message);
            //set success flag to false
            local.success = false;
        }
        if (StructKeyExists(local, "document")) {
            //close document
            local.document.close();
        }
        if (StructKeyExists(local, "outStream")) {
            //close outStream
            local.outputStream.close();
        }
        if (local.success && arguments.writeToBrowser) {
            _openPdf(filePath=local.filePath);
        }
    </cfscript>
</cffunction>
<!---function for opening pdf in browser--->
<cffunction name="_openPdf" output="no" returntype="void">
    <cfargument name="filePath" type="string" required="yes">
    <cfcontent type="application/pdf" file="#arguments.filePath#" deletefile="yes">
</cffunction>

Content of the cfc file, that should be named PdfPageEventHandler.cfc:

<cfcomponent output="false" hint="iText Event handler used to add headers, footers, etc.">
<!---1. INIT FUNCTION--->
    <cffunction name="init" access="public" returntype="PdfPageEventHandler" output="false">
        <!---come up with your own arguments that you want to have access to in onEndPage etc--->
        <cfargument name="config" type="any" required="true" hint="custom config" />
        <!---make sure the config is accessable by other functions--->
        <cfset variables.config = arguments.config>
        <cfreturn this />
    </cffunction>
<!---2. ON END PAGE - the function that is in focus in this example--->
    <cffunction name="onEndPage" access="public" returntype="void" output="true" hint="Called when a page is finished, just before being written to the document.">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <!---edit below to make your own header / footer--->
        <cfscript>
            local.cb = arguments.writer.getDirectContent();
            local.cb.saveState();
            local.cb.beginText();
            local.cb.setColorFill(variables.config.myCustomFooter.color);
            local.cb.setFontAndSize(variables.config.myCustomFooter.font, variables.config.myCustomFooter.size);
            local.cb.setTextMatrix(arguments.document.left(), arguments.document.bottom() - 10);
            local.text = "#variables.config.myCustomFooter.text# #arguments.writer.getPageNumber()#";
            local.cb.showText(local.text);
            local.cb.endText();
            local.cb.restoreState();
        </cfscript>
    </cffunction>
<!---3. OTHER FUNCTIONS THAT MUST EXIST (at least in this example)--->
    <cffunction name="onOpenDocument" access="public" returntype="void" output="false" hint="Called when the document is opened.">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <!---To be implemented: Code called when the document is opened--->
    </cffunction>
    <cffunction name="onCloseDocument" access="public" returntype="void" output="false" hint="Called when the document is closed">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <!---To be implemented: Code called when the document is closed--->
    </cffunction>
    <cffunction name="onStartPage" access="public" returntype="void" output="false" hint="Called when a page is initialized.">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <!---To be implemented: Code called when a page is initialized--->
    </cffunction>
    <cffunction name="onParagraph" access="public" returntype="void" output="false" hint="Called when a Paragraph is written">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <cfargument name="paragraphPosition" type="numeric" required="true" hint="The position the chapter will be written to. Value is a java float" />
        <!---To be implemented: Code called when paragraph is written --->
    </cffunction>
    <cffunction name="onParagraphEnd" access="public" returntype="void" output="false" hint="Called when a Paragraph is written">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <cfargument name="paragraphPosition" type="numeric" required="true" hint="The position the chapter will be written to. Value is a java float" />
        <!---To be implemented: Code called on end of paragraph is written--->
    </cffunction>
<!---4. FUNCTIONS THAT ONLY NEEDS TO EXIST IF YOU DO SOMETHING THAT TRIGGERS THEM
    <cffunction name="OnChapter" access="public" returntype="void" output="false" hint="Called when a Chapter is written">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <cfargument name="paragraphPosition" type="numeric" required="true" hint="The position the chapter will be written to. Value is a java float" />
        <cfargument name="title" type="any" required="true" hint="Title of the chapter. Instance of com.lowagie.text.Paragraph" />
        <!---To be implemented: Code called when a Chapter is written--->
    </cffunction>
    <cffunction name="onChapterEnd" access="public" returntype="void" output="false" hint="Called when the end of a Chapter is reached">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <cfargument name="position" type="numeric" required="true" hint="The position of the end of the chapter. Value is a java float" />
        <!---To be implemented: Code called when the end of a Chapter is reached--->
    </cffunction>
    <cffunction name="onGenericTag" access="public" returntype="void" output="false" hint="Called when a Chunk with a generic tag is written">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <cfargument name="rect" type="any" required="true" hint="The Rectangle containing the Chunk. Instance of com.lowagie.text.Rectangle" />
        <cfargument name="text" type="string" required="true" hint="The text of the tag" />
        <!---To be implemented: Code called when a Chunk with a generic tag is written--->
    </cffunction>
    <cffunction name="onSection" access="public" returntype="void" output="false" hint="Called when a Section is written">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <cfargument name="paragraphPosition" type="numeric" required="true" hint="The position the chapter will be written to. Value is a java float" />
        <cfargument name="depth" type="numeric" required="true" hint="The number depth of the Section. Value is a java int" />
        <cfargument name="title" type="any" required="true" hint="Title of the section. Instance of com.lowagie.text.Paragraph" />
        <!---To be implemented: Code called when a Section is written--->
    </cffunction>
    <cffunction name="onSectionEnd" access="public" returntype="void" output="false" hint="Called when the end of a Section is reached.">
        <cfargument name="writer" type="any" required="true" hint="Writer for the target pdf. Instance of com.lowagie.text.pdf.PdfWriter" />
        <cfargument name="document" type="any" required="true" hint="Document for target pdf. Instance of com.lowagie.text.Document" />
        <cfargument name="paragraphPosition" type="numeric" required="true" hint="The position the chapter will be written to. Value is a java float" />
        <cfargument name="depth" type="numeric" required="true" hint="The number depth of the Section. Value is a java int" />
        <cfargument name="title" type="numeric" required="true" hint="Title of the section. Instance of com.lowagie.text.Paragraph" />
        <!---To be implemented: Code called when the end of a Section is reached--->
    </cffunction>
    --->
</cfcomponent>

UPDATE!

Here's that same cfc, but in pure cfscript, for you that rather prefer that:

<cfscript>
    component output="false"  hint="iText Event handler used to add headers, footers, etc." {
        property name="config" type="struct";
        //1. INIT FUNCTION
        public PdfPageEventHandler function init(required any config) {
            //make sure the config is accessable by other functions
            variables.config = arguments.config;
            return this;
        }
    //2. ON END PAGE - the function that is in focus in this example
        public void function onEndPage(required any writer, required any document) {
            //Called when a page is finished, just before being written to the document
            //edit below to make your own header / footer
            local.cb = arguments.writer.getDirectContent();
            local.cb.saveState();
            local.cb.beginText();
            local.cb.setColorFill(variables.config.myCustomFooter.color);
            local.cb.setFontAndSize(variables.config.myCustomFooter.font, variables.config.myCustomFooter.size);
            local.cb.setTextMatrix(arguments.document.left(), arguments.document.bottom() - 10);
            local.text = "#variables.config.myCustomFooter.text# #arguments.writer.getPageNumber()#";
            local.cb.showText(local.text);
            local.cb.endText();
            local.cb.restoreState();
        }
    //3. OTHER FUNCTIONS THAT MUST EXIST (at least in this example)
        public void function onOpenDocument(required any writer, required any document) {
            //Called when the document is opened
        }
        public void function onCloseDocument(required any writer, required any document) {
            //Called when the document is closed
        }
        public void function onStartPage(required any writer, required any document) {
            //Called when a page is initialized
        }
        public void function onParagraph(required any writer, required any document, required numeric paragraphPosition) {
            //Called when a Paragraph is written
            //paragraphPosition - The position the chapter will be written to. Value is a java float
        }
        public void function onParagraphEnd(required any writer, required any document, required numeric paragraphPosition) {
            //Called when a Paragraph is written
            //paragraphPosition - The position the chapter will be written to. Value is a java float
        }
    //4. FUNCTIONS THAT ONLY NEEDS TO EXIST IF YOU DO SOMETHING THAT TRIGGERS THEM
        public void function OnChapter(required any writer, required any document, required numeric paragraphPosition, required any title) {
            //Called when a Chapter is written
            //paragraphPosition - The position the chapter will be written to. Value is a java float
            //title - Title of the chapter. Instance of com.lowagie.text.Paragraph
        }
        public void function onChapterEnd(required any writer, required any document, required numeric position) {
            //Called when the end of a Chapter is reached
            //position - The position of the end of the chapter. Value is a java float
        }
        public void function onGenericTag(required any writer, required any document, required any rect, required string text) {
            //Called when a Chunk with a generic tag is written
            //rect - The Rectangle containing the Chunk. Instance of com.lowagie.text.Rectangle
            //text - The text of the tag
        }
        public void function onSection(required any writer, required any document, required numeric paragraphPosition, required numeric depth, required any title) {
            //Called when a Section is written
            //paragraphPosition - The position the section will be written to. Value is a java float
            //depth - The number depth of the Section. Value is a java int
            //title - Title of the section. Instance of com.lowagie.text.Paragraph
        }
        public void function onSectionEnd(required any writer, required any document, required numeric paragraphPosition, required numeric depth, required any title) {
            //Called when the end of a Section is reached
            //paragraphPosition - The position the section will be written to. Value is a java float
            //depth - The number depth of the Section. Value is a java int
            //title - Title of the section. Instance of com.lowagie.text.Paragraph
        }
    }
</cfscript>

Upvotes: 1

Related Questions