Tuesday, October 21, 2008

Taking the View to PDF example to the next step.

As promised, here are some enhancements to make the PDF of a view a little nicer on the eyes.

The example agent enclosed improves on the previous version by;

  • handling a view with one category or a flat view
  • handling a view that has hidden, icon and total detail columns
  • adding a header image (from SuperNTF)
  • adding a footer with page numbers
I thought about updating the Pdf Document Library to allow a bit better control of cell padding and border colours of tables. In the end I decided against it. I really wanted to use what I had already created to prove that even with limited functionality you can still produce something that looks good enough.

Remember, if you have complicated reports or want fine grained control then write directly to iText. I also wanted to demonstrate that the PDF generation side is the easy bit. This means that you can spend more time writing the domino parts and tweeking for your formats than writing the PDF stuff.

The code is attached here and extracts below.

// get the date time for the report

DateTime dt = session.createDateTime("Today");

dt.setNow();


// the generic data object

PdfData data = new PdfData();


// where to put the file

PdfDocumentConfig config = new PdfDocumentConfig("Text",

"ViewByTitle.pdf", "c:\\pdfs");


/*

* Set up some of the styles that

* we will use in the pdf document.

*/


// the style for the database and view title

Style styleViewTitle = new Style("viewTitle", Style.TYPEFACE_HELVETICA,

18);

styleViewTitle.setDecoration(Style.DECORATION_BOLD);

styleViewTitle.setAlign(Style.ALIGN_CENTER);

styleViewTitle.setBackgroundColour(232, 232, 232);

styleViewTitle.setColor(0, 94, 187);


// the style for the date stamp

Style styleDate = new Style("date", Style.TYPEFACE_HELVETICA, 10);

styleDate.setAlign(Style.ALIGN_RIGHT);

styleDate.setBackgroundColour(Color.RED);


// the style for the view column titles

Style styleColumnTitle = new Style("columnsTitle",

Style.TYPEFACE_HELVETICA, 12);

styleColumnTitle.setDecoration(Style.DECORATION_BOLD);

styleColumnTitle.setBackgroundColour(232, 232, 232);

styleColumnTitle.setColor(0, 94, 187);


// the style for the column categories

Style styleColumnCat = new Style("columnsCat", Style.TYPEFACE_HELVETICA,

12);

styleColumnCat.setDecoration(Style.DECORATION_BOLD);

styleColumnCat.setBackgroundColour(247, 247, 247);

styleColumnCat.setColor(255, 153, 0);


// the default style for the view data

Style styleViewDefault = new Style("default", Style.TYPEFACE_HELVETICA,

10);


/*

* set header image - used the image from the superNTF nsf

*/

HeaderImage headerImage = new HeaderImage();

headerImage.addImage("c:\\pdfs\\BrandBanner.gif");

headerImage.setImageScale(HeaderImage.IMAGE_NO_SCALE, false);


// create a header to write the date into

HeaderTwoColumns header = new HeaderTwoColumns(50, 50);

header.setLeftColumn(new CellElement("", styleDate));

header.setRightColumn(new CellElement(dt.getDateOnly(), styleDate));

header.setStyle(styleDate);

header.setHeight(100);


// create a footer

FooterTwoColumns footer = new FooterTwoColumns(50, 50);

footer.setLeftColumn(new CellElement(

"http://palmerweb.blogspot.com - http://www.scius.com.au"));

footer.setRightColumn(new CellElement("", styleDate));

footer.setStyle(styleViewDefault);

footer.setPageNum(new PageNumbersElement(styleDate));

footer.includePageNumbers(true,

FooterTwoColumns.PAGE_NUMBERS_RIGHT);


// getting the notes handles

AgentContext ctx = session.getAgentContext();

Database db = ctx.getCurrentDatabase();


// this is the view

View viewMembers = db.getView("vw-docsbytitle");

// read the columns to get the report headings

Vector cols = viewMembers.getColumns();


// filter out the columns that we are not interested in

// such as icons, hidden, total details and categories

Vector colsToRead = new Vector();

for (int x = 0; x < cols.size(); x++) {

ViewColumn col = (ViewColumn) cols.get(x);

if (col.isIcon() == false && col.isCategory() == false

&& col.isHidden() == false

&& col.isHideDetail() == false) {

colsToRead.add(col);

}

}


// create a table based on the number of columns

TableElement tb = new TableElement(colsToRead.size());


/*

* create the heading (repeating) rows for the table.

*/

// database and view title

RowElement rw = new RowElement();

CellElement title = new CellElement(db.getTitle() + " - "

+ viewMembers.getName() + "\n\n", styleViewTitle);

title.setCellspan((colsToRead.size()));

rw.addCell(title);

rw.setTableHeading(true);

tb.AddRow(rw);

// column headings

rw = new RowElement();

for (int x = 0; x < colsToRead.size(); x++) {

ViewColumn col = (ViewColumn) colsToRead.get(x);

rw

.addCell(new CellElement(col.getTitle() + "\n\n",

styleColumnTitle));

}

rw.setTableHeading(true);

tb.AddRow(rw);

/*

* populate the table with the view data

*/

ViewNavigator viewNav = viewMembers.createViewNav();

ViewEntry rowData = viewNav.getFirst();


while (!(rowData == null)) {


rw = new RowElement();

Vector viewCol = rowData.getColumnValues();


// check to see if this is a category

if (rowData.isCategory()) {


CellElement catCell = new CellElement("\n"

+ viewCol.get(1).toString() + "\n\n", styleColumnCat);

catCell.setCellspan(colsToRead.size());

rw.addCell(catCell);


} else if (rowData.isDocument()) {

for (int x = 0; x < colsToRead.size(); x++) {

ViewColumn col = (ViewColumn) colsToRead.get(x);

int pos = col.getPosition() - 1;

rw.addCell(new CellElement(viewCol.get(pos).toString(),

styleViewDefault));


}

}


tb.AddRow(rw);

rowData = viewNav.getNext(rowData);

}


// add the table to the data object

data.add(tb);


// overide the default layout

LayoutElement layout = new LayoutElement(LayoutElement.PAGESIZE_A4,

LayoutElement.ORIENTATION_LANDSCAPE, 10, 10, 10, 10, 40, 50);


// add the image to the layout

layout.setHeaderImage(headerImage);

layout.setFooter(footer);

layout.setHeader(header);


// configure with the config, data and layout

PdfDocument pdf = new PdfDocument(config, data, layout);


// go

pdf.createDocument();


You should be able to take the agent code and just change the view name and you'll have a nicer looking PDF of your views - providing you like the colours and images. In the course of building this examples I made a few assumptions about the number of categories, and location of hidden columns. You might need to tweek the agent code if your views have more than one category or have totals and hidden columns in different locations. Its not just a drop in and works for every scenario.

What's next ?

I guess a notes document to PDF would be a nice example, so that's what will be in the pipeline. However, impending work commitments mean that it's highly likely that it won't be in the near future.

Monday, October 20, 2008

Notes View to PDF in 60 seconds

Watch the movie to see how to create a pdf of a view in 60 seconds.


I've produced a movie to demonstrate how easy it is to write an agent that creates a pdf of a view using my new library. I chose SuperNTF as it was the first database that had a flat view and a selection of documents. I copied and pasted enough entries to give me two pages so that you can see repeating headers. It's not quite as entertaining as 'Gone in 60 seconds' but does show you how quickly you can implement this in your own databases.


Below is the example code from the movie that simply dumps the content of a designated flat view to a table in a pdf. I've updated the library to Beta 7 to contain a minor enhancement to allow you to specify rows that are designated as headers. These header rows (yes you can specify more than one) are repeated on each page as the content forces a page break. Again, most of the heavy lifting is done by iText.


The Code

// the generic data object

PdfData data = new PdfData();

// where to put the file

PdfDocumentConfig config = new PdfDocumentConfig("Text",

"agent-test.pdf", "c:\\temp");


// getting the notes handles

AgentContext ctx = session.getAgentContext();

Database db = ctx.getCurrentDatabase();


// this is the view

View viewMembers = db.getView(" - insert your view alias here - ");

// get the column names for a header

Vector cols = viewMembers.getColumnNames();

// create a table based on the number of columns

TableElement tb = new TableElement(cols.size());

// start populating with the column names

RowElement rw = new RowElement();

for (int x = 0; x < cols.size(); x++) {

rw.addCell(new CellElement(cols.get(x).toString()));

}

// mark this as a table header to be repeated

rw.setTableHeading(true);

tb.AddRow(rw);

// start populating the table with the view data

ViewNavigator viewNav = viewMembers.createViewNav();

ViewEntry rowData = viewNav.getFirst();


while (!(rowData == null)) {

rw = new RowElement();

Vector viewCol = rowData.getColumnValues();


for (int y = 0; y < viewCol.size(); y++) {

rw.addCell(new CellElement(viewCol.get(y).toString()));

}

tb.AddRow(rw);

rowData = viewNav.getNext(rowData);

}

// add the table to the data object

data.add(tb);

// overide the default layout

LayoutElement layout =

new LayoutElement(LayoutElement.PAGESIZE_A4,

LayoutElement.ORIENTATION_LANDSCAPE,

10, 10, 10, 10, 10, 10);

// configure with the config, data and layout

PdfDocument pdf = new PdfDocument(config, data, layout);

// go

pdf.createDocument();


} catch (Exception e) {

e.printStackTrace();

} finally {

}


I've seen the move so how can I use this ?

1. Download Beta 7, and extract the updated jar file to a directory c:\lib

2. Download The JavaAgentPdf code

3. Create a new Java agent in your database, add the iText-2.0.8 jar and the new scius-pdfdoc.beta7.jar

3. Replace the JavaAgent code with the downloaded version and update the view name, pdf filename and pdf directory.


Enhancing the example further.

To keep this example short and under 30 lines, I have skipped of some of the things that you should add in for a production implementation, such as dealing with exceptions and checking that the domino objects are not null. However, for a proof-of-concept - it might be adequate. You would also need to write a few more lines to account for a categorised views, hidden columns and possibly the final enhancement would be to make the report look nice with some styles.


Stay tuned because the next post might have an example that produces a pdf that is a bit nicer on the eyes.


Update : The link to the zip file has changed. I found a bug with Beta7 and ParagraphElements.



Tuesday, October 07, 2008

A simple java library for creating pdfs

Here is a free, lightweight, simple to use library that sits on top of the iText library and which helps you quickly create PDF documents with java code.

I've created this library for my own applications and was toying with what to do with the library. After listening to Taking Notes Episode 86 I have decided to give it away and make it open source. After all, I'd found this useful so someone else might. So here I am, a guy sitting at a desk writing code, throwing it at a wall.

What is iText ?

If you've not heard of iText, it's a comprehensive open source Java library that you can use to create and manipulate PDF documents. You can use it to do pretty much anything from creating a regular document, to reports to creating maps and bar charts.

Why did you create another layer on iText ?

In the course of using the library I found that there were some simple and common things that I always wanted to do that were a little more complicated than I'd like. Things like adding page numbers, or calculating margins when you change the page orientation.

So I started adding a layer that would shield me from that complexity for the simple documents that I needed to create.

My library is fairly simplistic, for example, I only support Helvetica, Times Roman and Courier fonts. I only supports A4 and Letter page sizes. I didn't want to rewrite the entire iText library, just provide a little quicker access to the stuff I would need to change on a regular basis.

If you need to create A4 (or Letter) Pdf documents that are either Landscape or Portrait. If you need to have a header with an image and footers with page numbers and If you need to include paragraphs and tables with basic fonts and formatting (such a bold, italics and colors) then this library might help you.

If you have much more complex requirements then you should probably look at using iText directly or using one of the many Pdf libraries that are available.

However, if this almost meets your needs and hits that sweet spot, like it did for me, then it will save a bit of time. If it is almost what you need, then you have access to the source code to make your own variation.

Show me how I can use it

A simple example and the result.

/*

* Create a config object to contain file location and

* some meta data settings

*/

config = new PdfDocumentConfig("Example 3","example3.pdf","examples");

config.setReportSubject("Example 3 - Image banner");

config.setReportAuthor("Scius and iText");

/*

* Creating a header image

*/

headerImage = new HeaderImage();

headerImage.addImage(new String("examples/top.jpg"));

headerImage.setImageScale(HeaderImage.IMAGE_PAGE_SCALE, true);

/*

* Create a layout, size, orientation, margins, header and footer

*/

layout = new LayoutElement(

LayoutElement.PAGESIZE_A4,

LayoutElement.ORIENTATION_PORTRAIT,

20,20,10,10,100,100

);

// add the image to the layout

layout.setHeaderImage(headerImage);

/*

* Create the PDF content, the data written to the PDF

*/

data = new PdfData();

data.add(new ParagraphElement("Hello World"));

/*

* Create a instance of the PDF Document writer, using

* the config, data and layout

*/

pdf = new PdfDocument(config, data, layout);

/*

* generate the document

*/

pdf.createDocument();



A more complicated example and the result

/*

* Create a config object to contain file location and

* some meta data settings.

*/

config = new PdfDocumentConfig("Example 5","example5.pdf","examples");

config.setReportSubject("Example 4 - Tables");

config.setReportAuthor("Scius and iText");

/*

* Creating a header image.

*/

headerImage = new HeaderImage();

headerImage.addImage(new String("examples/top.jpg"));

headerImage.setImageScale(HeaderImage.IMAGE_PAGE_SCALE, true);

/*

* Set the default style and page numbers style.

*/

Style defaultStyle = new Style("default",Style.TYPEFACE_TIMES_ROMAN,15);

// styles for the table

Style tableStyle = new Style("default", Style.TYPEFACE_HELVETICA, 10);

tableStyle.setColor(Color.BLUE);

Style tableHeaderStyle = new Style("default", Style.TYPEFACE_HELVETICA, 10);

tableHeaderStyle.setDecoration(Style.DECORATION_BOLD);

tableHeaderStyle.setBackgroundColour(192, 192, 192);

Style stylePageNumbers = new Style("pagenumbers", Style.TYPEFACE_TIMES_ROMAN, 15);

stylePageNumbers.setAlign(Style.ALIGN_RIGHT);


/*

* Create a two column footer.

*/

footer = new FooterTwoColumns(50,50);

footer.setLeftColumn(new CellElement("Example 4"));

footer.setRightColumn(new CellElement("",stylePageNumbers));

footer.setStyle(defaultStyle);

footer.setPageNum(new PageNumbersElement(stylePageNumbers));

footer.includePageNumbers(true,FooterTwoColumns.PAGE_NUMBERS_RIGHT);

/*

* Create a layout, size, orientation, margins, header and footer

*/

layout = new LayoutElement(

LayoutElement.PAGESIZE_A4,

LayoutElement.ORIENTATION_PORTRAIT,

20,20,10,10,100,100

);

layout.setHeaderImage(headerImage);

layout.setFooter(footer);

/*

* Create the PDF content, the data written to the PDF.

*/

data = new PdfData();

data.add(new ParagraphElement("Hello World", defaultStyle));

data.add(new ParagraphElement("table of authors\n\n", defaultStyle));

// add tabular information

TableElement table = new TableElement(3);

// define the column widths

float[] wd = {50f, 25f,25f};

table.setColumnWidths(wd);

// start adding data

RowElement row = new RowElement();

row.addCell(new CellElement("Author",tableHeaderStyle));

CellElement ce = new CellElement("Book Details",tableHeaderStyle);

ce.setCellspan(2);

row.addCell(ce);

table.AddRow(row);

RowElement row2 = new RowElement();

row2.addCell(new CellElement("Bruno Lowgie", tableStyle));

row2.addCell(new CellElement("iText in Action", tableStyle));

row2.addCell(new CellElement("http://www.lowagie.com/iText",tableStyle));

table.AddRow(row2);

RowElement row3 = new RowElement();

row3.addCell(new CellElement("Bruce Eckel", tableStyle));

row3.addCell(new CellElement("Thinking in Java", tableStyle));

row3.addCell(new CellElement("http://mindview.net/Books/TIJ4",tableStyle));

table.AddRow(row3);

// add the table to the data transfer object

data.add(table);

/*

* Create a instance of the PDF Document writer, using

* the config, data and layout.

*/

pdf = new PdfDocument(config, data, layout);

/*

* creates the pdf document file.

*/

pdf.createDocument();


Where can I get it ?

Here...

What's in the zip file ?

The zip file contains;

  • A source folder, complete with 5 examples.
  • A library folder with my library (scius-pdfdoc-beta6.jar) and the iText jar,
  • A doc folder with a document that describes the library and how to get started with it in eclipse,
  • A javadoc foder with the javadoc for the source code.

What's next ?


If I get enough interest then I'll write a step by step guide, similar to the eclipse one, showing you how to using the library in Notes and Domino.