Showing posts with label pdf. Show all posts
Showing posts with label pdf. Show all posts

Monday, 25 April 2011

Create PDF and RTF reports using Jasper API

Using Japer, you can create reports in various formats like CSV file, HTML file, PDF file, RTF file, Excel file, XML file etc. First of all, the report needs to be designed using a report designer. I was using the open source report designing tool called, iReport. You can produce a report using the data from a database, parameters, variable expressions etc. If you are generating the report from a custom class file (DAO), include it in the class path of iReport. Using the iReport designing tool, you’ll be able to create a report as a JRXML/XML file; compile the report creating a new file with extension, .jasper called as ‘Jasper file’. We will be using this Jasper file in our java code.
Download the Jasper API with all its dependent jar’s and invoke the report using the API. Here is a sample to generate PDF and RTF reports. Generating other formats are very similar to this. Here I am using a custom datasource inplemented from JRDataSource for generating the reports. Since I was writing a web service to generate reports, I wasn’t rendering the report but returning the report in byte array. The client progran would use the FileOutputStream to render the report from byte array or could directly feed the byte array to the OutputStream of the HttpServletResponse.

public byte[] buildRtfResume(final String ssnNumber) {
ResumeEngineImpl.log.debug("Building RTF Resume for " + ssnNumber);
byte[] rtfResume = null;
try {
final Applicant applicant = this.getApplicantBySsn(ssnNumber);
final JasperPrint jasperPrint = this.renderCV(applicant);
final JRRtfExporter rtfExporter = new JRRtfExporter();
final ByteArrayOutputStream rtfStream = new ByteArrayOutputStream();
rtfExporter.setParameter(JRExporterParameter.JASPER_PRINT,jasperPrint);
rtfExporter.setParameter(JRExporterParameter.OUTPUT_STREAM,rtfStream);
rtfExporter.exportReport();
rtfResume = rtfStream.toByteArray();
} catch (final JRException e) {
ResumeEngineImpl.log.error(e.getMessage(), e);
} catch (final RuntimeException e) {
ResumeEngineImpl.log.error(e.getMessage(), e);
}
return rtfResume;
}

public byte[] buildPdfResume(final String ssnNumber) {
ResumeEngineImpl.log.debug("Building PDF Resume for " + ssnNumber);
byte[] pdfResume = null;
try {
final Applicant applicant = this.getApplicantBySsn(ssnNumber);
final JasperPrint jasperPrint = this.renderCV(applicant);
pdfResume = JasperExportManager.exportReportToPdf(jasperPrint);
} catch (final JRException e) {
ResumeEngineImpl.log.error(e.getMessage(), e);
} catch (final RuntimeException e) {
ResumeEngineImpl.log.error(e.getMessage(), e);
}
return pdfResume;
}

JasperPrint object represents the output, which can be viewed, printed or exported to many different formats.JasperFillManager class is used for report filling process. It takes three arguments to fill a report; Compiled report design, Parameters and Datasources.

public JasperPrint renderCV(Applicant applicant){
..........
..........
final JasperReport jasperReport = (JasperReport) JRLoader.loadObject(this.getClass().getClassLoader().getResourceAsStream("jerry/jacob/reports/resume.jasper"));
final JasperReport empHistSubReport = (JasperReport) JRLoader.loadObject(this.getClass().getClassLoader().getResourceAsStream("jerry/jacob/reports/EmploymentSubReport.jasper"));
final JasperReport edHistSubReport = (JasperReport) JRLoader.loadObject(this.getClass().getClassLoader().getResourceAsStream("jerry/jacob/reports/EducationSubReport.jasper"));
.........
final EmpHistDataSource empHistDataSource = new EmpHistDataSource(empHists);
final EducationHistDataSource edHistDataSource = new EducationHistDataSource(eduHists);
.........
final Map parameters = new HashMap();
// add sub reports to map
parameters.put("EmploymentSubReport", empHistSubReport);
parameters.put("EducationSubReport", edHistSubReport);
// add data sources to map
parameters.put("EmploymentDataSource", empHistDataSource);
parameters.put("EducationDataSource", edHistDataSource);
return JasperFillManager.fillReport(jasperReport, parameters,applicantDataSource);
}

Getting the word document:
public byte[] buildDocResume(final String ssnNumber) {

final String sourceMethod = "buildDocResume";
ResumeEngineImpl.log.entering(ResumeEngineImpl.sourceClass,
sourceMethod, ssnNumber);
byte[] docResume = null;
try {
final Seeker seeker = this.getSeekerBySsn(ssnNumber);
final JasperPrint jasperPrint = this.renderResume(seeker);

final JRDocxExporter docExporter = new JRDocxExporter();
final ByteArrayOutputStream docStream = new ByteArrayOutputStream();
docExporter.setParameter(JRDocxExporterParameter.JASPER_PRINT,
jasperPrint);
docExporter.setParameter(JRDocxExporterParameter.OUTPUT_STREAM,
docStream);
docExporter.setParameter(JRDocxExporterParameter.FLEXIBLE_ROW_HEIGHT, Boolean.TRUE);
docExporter.exportReport();
docResume = docStream.toByteArray();
} catch (final JRException e) {
ResumeEngineImpl.log.logp(Level.WARNING,
ResumeEngineImpl.sourceClass, sourceMethod,
"Exception caught:", e);
} catch (final RuntimeException e) {
ResumeEngineImpl.log.logp(Level.WARNING,
ResumeEngineImpl.sourceClass, sourceMethod,
"Exception caught:", e);
}

return docResume;
}

Tip: The generated word document will be formatted using several nested tables. If the property, JRDocxExporterParameter.FLEXIBLE_ROW_HEIGHT, is set to false, the table rows do not increase in height automatically and the user has to enlarge them manually in word.

Wednesday, 13 April 2011

PDF Generation with Big Faceless Report Generator

Big Faceless Report Generator is a commercial Java API for generating PDF files from XML input. The report generator is built on the Big Faceless Java PDF library. It has a very easy to use API, but the one thing that I like most about it is, for it is more than a bargain for the price you pay for it. It easily out performs the open source PDF generators (iText and XSL FOP) and also has a lot of intersting features.

  • HTML-style Tables - auto-sized nested tables with full control over padding, margins and borders
  • Support for CSS2 Style sheets
  • Create inline graphs and charts with XML, direct from the database
  • Includes a servlet and a servlet filter for simple generation of PDF's from XML or JSP pages.
  • Auto pagination of content with page headers and footers
  • Familiar HTML syntax - <a>, <ul>, <p>, <table>, <td> and so on - simpler and faster than FOP
  • Unicode support, Encryption, TrueType fonts, Barcodes and more
  • Load existing PDF documents as templates
  • Digitally sign documents
  • Create and edit interactive Forms, or "AcroForms"
The following is an example of how to use the Report generation library to create PDF files using an XML for input Data, and an XSL as a template. Follow these steps to implement the example
  1. Download Big Faceless Report generator from here. You only have to include the bforeport.jar file in your classpath.
  2. Create the input XML file as shown below:

    <?xml version="1.0" encoding="UTF-8"?>
    <Org>
    <Employee>
    <firstName>Asif</firstName>
    <lastName>Mandvi</lastName>
    <dateOfBirth>1/1/1900</dateOfBirth>
    </Employee>
    <Employee>
    <firstName>James</firstName>
    <lastName>Baker</lastName>
    <dateOfBirth>1/1/1900</dateOfBirth>
    </Employee>
    <Employee>
    <firstName>Jon</firstName>
    <lastName>Stewart</lastName>
    <dateOfBirth>1/1/1900</dateOfBirth>
    </Employee>
    <Employee>
    <firstName>Stephen</firstName>
    <lastName>Colbert</lastName>
    <dateOfBirth>1/1/1900</dateOfBirth>
    </Employee>
    <Employee>
    <firstName>Samantha</firstName>
    <lastName>Bee</lastName>
    <dateOfBirth>1/1/1900</dateOfBirth>
    </Employee>
    <Employee>
    <firstName>Jon</firstName>
    <lastName>Oliver</lastName>
    <dateOfBirth>1/1/1900</dateOfBirth>
    </Employee>
    </Org>

  3. Create the template for the PDF(XSL) as shown below.

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" />
    <xsl:preserve-space elements="true" />
    <xsl:template match="Org">
    <pdf>
    <body>
    <table font-size="12pt" width="700px">
    <tr font-weight="bold">
    <td>First Name</td>
    <td>Last Name</td>
    <td>Date of Birth</td>
    </tr>
    <xsl:for-each select="/Org/Employee">
    <tr>
    <td>
    <xsl:value-of select="firstName" />
    </td>
    <td>
    <xsl:value-of select="lastName" />
    </td>
    <td>
    <xsl:value-of select="dateOfBirth" />
    </td>
    </tr>
    </xsl:for-each>
    </table>
    </body>
    </pdf>
    </xsl:template>
    </xsl:stylesheet>
  4. Transform the XML to the format required by the report generator: The XML file cannot be used as input to the report generator directly. It has to be first transformed into the required format. That is reason for using the XSL.

    public class ConvertXML {
    public void renderReport() {
    try {
    TransformerFactory factory = TransformerFactory.newInstance();
    StreamSource stylesheet = new StreamSource(new FileInputStream("c:\\pdf\\test.xsl"));
    Transformer transformer = factory.newTransformer(stylesheet);
    Source src = new StreamSource(new FileInputStream("c:\\pdf\\test.xml"));
    Result res = new StreamResult(new FileOutputStream("c:\\pdf\\intermediate.xml"));
    transformer.transform(src, res);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
  5. The following code can be used to generate the final PDF: The PDFGenerator first uses the ConvertXML to transform the input to the report generator input format (intermediate.xml), and then invokes the report generator.

    public class PDFGenerator {
    public void createPDF(String xmlfile, OutputStream out) {
    ReportParser parser;
    try {
    parser = ReportParser.getInstance();
    InputSource src = new InputSource(new FileInputStream(xmlfile));
    PDF pdf = (PDF) parser.parse(src);
    pdf.render(out);
    out.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    public static void main(String args[]) {
    PDFGenerator gen = new PDFGenerator();
    OutputStream os = null;
    try {
    ConvertXML cx = new ConvertXML();
    cx.renderReport();
    os = new FileOutputStream("c:\\pdf\\test.pdf");
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    }
    gen.createPDF("C:\\pdf\\intermediate.xml", os);
    }
    }



Split PDF files in Java using iText JAR

Consider the following code in java:

public static void splitPDF(InputStream inputStream,
OutputStream outputStream, int fromPage, int toPage) {
Document document = new Document();
try {
PdfReader inputPDF = new PdfReader(inputStream);

int totalPages = inputPDF.getNumberOfPages();

//make fromPage equals to toPage if it is greater
if(fromPage > toPage ) {
fromPage = toPage;
}
if(toPage > totalPages) {
toPage = totalPages;
}

// Create a writer for the outputstream
PdfWriter writer = PdfWriter.getInstance(document, outputStream);

document.open();
PdfContentByte cb = writer.getDirectContent(); // Holds the PDF data
PdfImportedPage page;

while(fromPage <= toPage) {
document.newPage();
page = writer.getImportedPage(inputPDF, fromPage);
cb.addTemplate(page, 0, 0);
fromPage++;
}
outputStream.flush();
document.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (document.isOpen())
document.close();
try {
if (outputStream != null)
outputStream.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}

In above code, we have created a method splitPDF () that can be used to extracts pages out of a PDF and write it into another PDF. The code is pretty much self explanatory and is similar to the one to merge PDF files.
Thus, if you need to split an input.pdf (having 20 pages) into output1.pdf (1-12 pages of input.pdf) and output2.pdf (13-20 of input.pdf), you can call the above method as follow:

public static void main(String[] args) {
try {
MergePDF.splitPDF(new FileInputStream("C:\\input.pdf"),
new FileOutputStream("C:\\output1.pdf"), 1, 12);
MergePDF.splitPDF(new FileInputStream("C:\\input.pdf"),
new FileOutputStream("C:\\output2.pdf"), 13, 20);

} catch (Exception e) {
e.printStackTrace();
}
}

iText tutorial:Merge PDF files using iText JAR

Previous article Generating PDF files using iText JAR had described a nice and basic way of generating PDF files in Java using iTest JAR. It is a great starter tutorial for those who wants to start working with iText.
In one of the requirement, I had to merge two or more PDF files and generate a single PDF file out of it. I thought of implementing the functionality from scratch in iText, but then thought to google it and see if already someone have written code for what I was looking for. As expected, I got a nice implementation of java code that merges 2 or more PDF files using iText jar. I thought of dissecting the code in this post and give credit to original author of the post.

  1. Create a PdfReader for each input PDF file
    PdfReader pdfReader = new PdfReader(pdf);
  2. Create a document object to represent the PDF.
    Document document = new Document();
  3. Create a PdfWriter for the target OutputStream
    PdfWriter writer = PdfWriter.getInstance(document, outputStream);
  4. Select a font with which the page numbers will be written
    BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
  5. Create a a PdfContentByte object to hold the data of the merged PDF
    PdfContentByte cb = writer.getDirectContent();
  6. Add individual pages from the source to the target.
    document.newPage();
    pageOfCurrentReaderPDF++;
    currentPageNumber++;
    page1 = writer.getImportedPage(pdfReader, pageOfCurrentReaderPDF);
    cb.addTemplate(page1, 0, 0);
  7. Add page text at the bottom of the page
    cb.beginText();
    cb.setFontAndSize(bf, 9);
    cb.showTextAligned(PdfContentByte.ALIGN_CENTER, "Page " + currentPageNumber + " of " + totalPages, 520, 5, 0);
    cb.endText();
public class MergePDF {
public static void main(String[] args) {
try {
List<InputStream> pdfs = new ArrayList<InputStream>();
pdfs.add(new FileInputStream("c:\\pdf\\2.pdf"));
pdfs.add(new FileInputStream("c:\\pdf\\3.pdf"));
OutputStream output = new FileOutputStream("c:\\pdf\\merge.pdf");
MergePDF.concatPDFs(pdfs, output, true);
} catch (Exception e) {
e.printStackTrace();
}
}

public static void concatPDFs(List<InputStream> streamOfPDFFiles, OutputStream outputStream, boolean paginate) {

Document document = new Document();
try {
List<InputStream> pdfs = streamOfPDFFiles;
List<PdfReader> readers = new ArrayList<PdfReader>();
int totalPages = 0;
Iterator<InputStream> iteratorPDFs = pdfs.iterator();

// Create Readers for the pdfs.
while (iteratorPDFs.hasNext()) {
InputStream pdf = iteratorPDFs.next();
PdfReader pdfReader = new PdfReader(pdf);
readers.add(pdfReader);
totalPages += pdfReader.getNumberOfPages();
}
// Create a writer for the outputstream
PdfWriter writer = PdfWriter.getInstance(document, outputStream);

document.open();
BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
PdfContentByte cb = writer.getDirectContent(); // Holds the PDF
// data

PdfImportedPage page;
int currentPageNumber = 0;
int pageOfCurrentReaderPDF = 0;
Iterator<PdfReader> iteratorPDFReader = readers.iterator();

// Loop through the PDF files and add to the output.
while (iteratorPDFReader.hasNext()) {
PdfReader pdfReader = iteratorPDFReader.next();

// Create a new page in the target for each source page.
while (pageOfCurrentReaderPDF < pdfReader.getNumberOfPages()) {
document.newPage();
pageOfCurrentReaderPDF++;
currentPageNumber++;
page = writer.getImportedPage(pdfReader, pageOfCurrentReaderPDF);
cb.addTemplate(page, 0, 0);

// Code for pagination.
if (paginate) {
cb.beginText();
cb.setFontAndSize(bf, 9);
cb.showTextAligned(PdfContentByte.ALIGN_CENTER, "" + currentPageNumber + " of " + totalPages, 520, 5, 0);
cb.endText();
}
}
pageOfCurrentReaderPDF = 0;
}
outputStream.flush();
document.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (document.isOpen())
document.close();
try {
if (outputStream != null)
outputStream.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}

Thanks.