Sunday, July 06, 2014

How to print documents using Java ? How to use PJL or Ghostscript with Java for Printing ?

In this article we will be discussing the below listed topics.
  • How to print documents using Java ?
  • How to use PJL (Print job language) commands to send instructions to printer ?
  • How to use Ghostscript commands to send instructions to printer ?
  • How to staple or duplex print documents using printer in Java ?
  • Print pdf's using java.
  • How to check the printer properties using java ?
  • How to select media tray using java on the printer ?
  • How to staple documents on a printer using java code ?
  • Print PDF using PrinterJob in java
  • Print pdf in network printer using java
  • java printing - printing a pdf 
  • Using Java to Print PDF Documents
  • Printing PDF files from Java
  • How to print PDF files using java print API ? 
  • Use Printing Service in Java.
  • Working with Print Services and Attributes.


I had to work on  printing pdf documents and had some special requirements where in few documents were supposed to use one type of paper 
compared to other set of documents. Basically we wanted to control Tray selection during printing.
The plain java api though has commands to set printer trays I guess the driver did not support or am not sure of the exact reason but I couldn't do it within java.
So had to use some PJL commands and Ghost script to get this done with rest of the stuff in JAVA


Here is some sample code Which Prints the media trays for Default printer or can read a different printer as input and prints its attributes. Then displays all tray available and will ask user the tray to select Then will print an empty page for testing targeting that Tray number..


package com.rama.print.test;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.SimpleDoc;
import javax.print.attribute.AttributeSet;
import javax.print.attribute.HashAttributeSet;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.Media;
import javax.print.attribute.standard.MediaTray;
import javax.print.attribute.standard.PrinterName;

/**
 * @author TWreddy
 *
 * Prints the media trays for Default printer or can read a different printer as input and prints its attributes. 
 * Then display all tray available and will ask user the tray to select 
 * Then will print an empty page for testing targeting that Tray number..
 */
public class TestMyPrinter   {
 public static void main(String args[])throws Exception {
  printAllPrinters();
  System.out.println(" -----------------------------------------------------------");
  System.out.println(" Above is the list of printers accessible from your machine.");
  System.out.println(" -----------------------------------------------------------");
  System.out.println(" ");
  System.out.println(" ");
  // get default printer
  PrintService defaultPrintService = PrintServiceLookup.lookupDefaultPrintService();

  // suggest the use of the default printer
  
  System.out.println("Enter the printer name or press enter for default printer which is: ["+ defaultPrintService.getName() + "]?" );
  System.out.println("");
  
  // read from the console the name of the printer
   BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
   String printerName = bufferRead.readLine();

  
  // if there is no input, use the default printer
  if (printerName == null || printerName.equals("")) {
   printerName = defaultPrintService.getName();
  }

  // the printer is selected
  AttributeSet aset = new HashAttributeSet();
  aset.add(new PrinterName(printerName, null));
  // selection of all print services
  PrintService[] services =            PrintServiceLookup.lookupPrintServices(null,aset);
  Map<Integer, Media> trayMap = getAvailableTraysOnPrinter(services);

  
  System.out.println("Select tray target id : " +"\n");
  String mediaId = bufferRead.readLine();

  MediaTray selectedTray = (MediaTray) trayMap.get(Integer.valueOf(mediaId));
  System.out.println("Selected tray : " + selectedTray.toString() +"\n");

  System.out.println("Do you want to print a test page? [y/n] \n");
  
  String printPage = bufferRead.readLine();
  
  if (printPage.equalsIgnoreCase("Y")) {
   printDoc(services, selectedTray);
  }
  
  System.out.println(" Completed.");
 }

 /**
  *  Prints an empty document based on Tray you selected.
  * or you can print a pdf 
  * or you can output the contents that was sent to printer to another file for debugging.
         *  Comment or uncomment the code below depending on whether you want to test prniting 
         *  empty document (useful to test printer and  you dont want to waste paper)
         *  or print a actual document or want to see what content is exactly being sent to printer.
  * @param services
  * @param selectedTray
  */
 private static void printDoc(PrintService[] services,MediaTray selectedTray) {
  // we have to add the MediaTray selected as attribute
  

  
  
  // we create the printer job, it print a specified document with a
  // set of job attributes
  //DocPrintJob printJob = filePrinter.getPrintService().createPrintJob();
  DocPrintJob job = services[0].createPrintJob();

  
  try {
   System.out.println("Trying to print an empty page (Or uncomment code to Print PDF) on Media Tray: "+ selectedTray.toString());
   
   System.out.println(" Media tray selection only works for documents thats created on the fly . See PrintableDemo class below.");
   System.out.println(" For documents like pdf, word, text files...etc... I had to use Ghostscript and to that document add @PJL commands.");
   
   
   
   // we print using the selected attributes which is paper tray.
   //SET PRINTER PROPERTIES
   /* This approach works only for CASE1 and CASE3  described below . */
   PrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet();
   attributes.add(selectedTray);
   
   
   
 //CASE1: SIMPLE TEST where we try to print a document created on the fly with a simple String
 // we create a document that implements the printable interface . 
 // I just passed selectedTray so that if required print that on the paper for testing.
  Doc doc = new SimpleDoc(new PrintableDemo(selectedTray),DocFlavor.SERVICE_FORMATTED.PRINTABLE, null);
   
   
   
   
 //CASE2: TEST with actual documents . Here the tray selection doesnt work.
 /*  Somehow when I am using a PDF though I select tray 2 its sending to Tray1.
    But this same code when invoked with empty doc works ???
 NOTE: After quite testing I noticed that when we print a document the entire binary is sent 
   Directly to printer with no PCL commands for tray info or stapling etc... 
   In case of
 */
   
   //InputStream inputStream = new FileInputStream("C://coverPage.pdf");
   //InputStream inputStream = new FileInputStream("C://other.pdf");
   //InputStream inputStream = new FileInputStream("C://other.docx");
   //Doc doc = new SimpleDoc(inputStream, DocFlavor.INPUT_STREAM.AUTOSENSE ,null);
   
   
   
 //CASE3: Use ghost script and modify the pdf to ps file and to that add any PCL commands.
 //So that you can control tray selection , duplex printing, stapling and what not..
 //File psFile = modifyDocumentUsingGhostScriptAndPrint( new File("C://coverPage.pdf"));
 //InputStream inputStream = new FileInputStream(psFile);
 //Doc doc = new SimpleDoc(inputStream, DocFlavor.INPUT_STREAM.AUTOSENSE ,null);
 
   
  /** 
   * Want to see what you really sent to printer ??
   * 
   * PRINT TO FILE
   * You want to see what exactly is being sent to printer un comment this code
   * and it will write the entire Stream to a flie (which is want Printer would have received) 
  **/
   //File outputFile = new File("C://ContentSentToPrinter.txt");
   //attributes.add(new Destination(outputFile.toURI()));
   
   
   job.print(doc, attributes);
   
   System.out.println(" Done processing ......");
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

/**
  *  Transforms the PDF file to Post script 
  *  and as a demo adds 
  *  - PJL commands to Print a particular tray
  *  - Adds PS commands to staple or duplex print.
  *  
  *   Bottom line you can add much more stuff... Just showing what all can be done..
  *   
  */
 public static File modifyDocumentUsingGhostScriptAndPrint(File pdfFile)throws Exception{
  File psFile = null;
  psFile = PostScriptConversion.convertToPS(pdfFile);
  PostScriptConversion.addAddtionalCommandsForPSFile(psFile);
  if(psFile == null){
   System.err.println(" Oops PS conversion didnt yeild us anything.");
   throw new RuntimeException(" Oops PS conversion didnt yeild us anything.");
  }
  return psFile; 
 } 
 
 
 
 
 private static Map<Integer, Media> getAvailableTraysOnPrinter(PrintService[] services) {
  // We store all the trays in a map
  Map<Integer, Media> trayMap = new HashMap<Integer, Media>(10);

  //We chose something compatible with the printable interface
  DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
  //

  for (PrintService service : services) {
   System.out.println(service);

   // we retrieve all the supported attributes of type Media
   // we can receive MediaTray, MediaSizeName, ...
   Object o = service.getSupportedAttributeValues(Media.class, flavor, null);
   if (o != null && o.getClass().isArray()) {
    for (Media media : (Media[]) o) {
     // we collect the MediaTray available
     if (media instanceof MediaTray) {
      System.out.println(" Use id number :"+ media.getValue() + "  For accessing: " + media + " - " + media.getClass().getName());
      trayMap.put(media.getValue(), media);
     }
    }
   }
  }
  return trayMap;
 }
 
 
 private static void printAllPrinters() {
  PrintRequestAttributeSet printRequestAttributes = new HashPrintRequestAttributeSet();
   //  printRequestAttributes.add(OrientationRequested.LANDSCAPE);
   //  printRequestAttributes.add(Sides.DUPLEX);
   //  printRequestAttributes.add(MediaSizeName.ISO_A4);
   //     printRequestAttributes.add(Finishings.STAPLE);
  PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, printRequestAttributes);
  for(PrintService printService :printServices){
   System.out.println(printService +" Name to use="+ printService.getName());
  }
 }
 
 
 private static void printPrinterProperties(String printName){
  AttributeSet aset = new HashAttributeSet();
  aset.add(new PrinterName(printName, null));
  PrintService[] services = PrintServiceLookup.lookupPrintServices(null, aset);
  for (int i = 0; i < services.length; i++) {
    PrintService service = services[i];
    System.out.println(service);
    //DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PAGEABLE;
    DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
    Object attributes = service.getSupportedAttributeValues(Media.class, flavor, null);
    if (attributes != null && attributes.getClass().isArray()) {
      for (Media media : (Media[]) attributes) {
        System.out.println(media + " ID: " + media.getValue() + "\t" + media.getClass().getName());
        /* if(media instanceof sun.print.Win32MediaTray){
         Win32MediaTray  win32Tray = (Win32MediaTray)media;
         System.out.println(win32Tray.winID);
        }*/
        
      }
    }
  }
  
 }
 
}
Here is the printable demo class that you can use to print empty document. This will be useful when testing printer and tray selection within the printer that way you can you same sheet of paper again and again.

package com.rama.print.test;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.print.PageFormat;
import java.awt.print.Printable;

import javax.print.attribute.standard.MediaTray;
/**
 * 
 * To print a page just on the fly without reading physical document.
 * good for testing 
 * @author TWreddy
 *
 */
public class PrintableDemo implements Printable {
 
 MediaTray selectedTray = null;
 public PrintableDemo(MediaTray trayToUse){
  selectedTray = trayToUse;
 }
 
 
 @Override
 public int print(Graphics pg, PageFormat pf, int pageNum) {
  
  //The printing system will call the Printable.print()  until we return NO_SUCH_PAGE
  if (pageNum > 0){
   //First time pageNum = 0...
   System.out.println("PrintableDemo.print pageNum="+ pageNum);
   return Printable.NO_SUCH_PAGE;
  }
  
  
  //User (0,0) is typically outside the imageable area, so we must translate
     // by the X and Y values in the PageFormat  to avoid clipping.
     Graphics2D g2d = (Graphics2D)pg;
     g2d.translate(pf.getImageableX(), pf.getImageableY());

     
     //For testing SAVE paper by setting messagOnDoc=""; that way only empty paper will get pulled from Tray with nothing being printed.   
     String messagOnDoc = "Hello  Your tray number was="+ this.selectedTray.getName() +" : "+this.selectedTray.getValue();
     // Now we perform our rendering
     pg.drawString(messagOnDoc, 100, 100);

     // tell the caller that this page is part
     // of the printed document
     return PAGE_EXISTS;
     
 }
}

Here is the code for Converting a PDF document to PostScript.
And in addition we can additional post script commands and also PJL commands
eg: for Duplex printing, Stapling of documents if printer supports.
The assumption is you have Ghost script installed on your local. (Else download and install ghost script. I used  Ghost script 8.61 )

package com.rama.print.test;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;

import org.apache.commons.io.FileUtils;

/**
 * @author TWreddy
 * Convert a PDF document to PostScript.
 * And in addtion we can additional post script commands and also PJL commands 
  * eg: for Duplex printing, Stapling of documents if printer supports.
 */
class PostScriptConversion extends Thread {
 
 //NOTE: To see how PJL looks print a document (withou ot java code)  and while doing 
 //Say print to file (its that check box on the print properties window). 
 // Which means you are exporting the entire document with all PJL commands 
 // that would have been sent to printer otherwise. 
 
 //ESC%-12345X@PJL is start of print job language.
 private static String PJL_START = String.valueOf((char) 27) + "%-12345X@PJL";
 private static String PJL_END = String.valueOf((char) 27) + "%-12345X";
 private static String PJL_POSTSCRIPT = "@PJL ENTER LANGUAGE = POSTSCRIPT";
 private static String PJL_COMMAND_TO_SELECT_PRINT_TRAY = "@PJL SET MEDIASOURCE = TRAY2";
 
 private static String POST_SCRIPT_COMMAND_FOR_DUPLEX_PRINTING = "<< /Duplex true >> setpagedevice";
  
 
 boolean isRunning;
 String command = null;
 Process process = null;
 int exitVal = -1;
 boolean processingComplete;
 
 public PostScriptConversion( String command) {
  this.command = command;
 }

 public void run() {
  try {
   System.out.println("executing: " + command);
   process = Runtime.getRuntime().exec(command);

   // Spawn thread to read output of spawned program  --  
   new Thread() {
    public void run() {
     this.setName("Process-InputStream");
     // hook into output from spawned program
     final InputStream is = process.getInputStream();
     final InputStreamReader isr = new InputStreamReader(is);
     final BufferedReader br = new BufferedReader(isr, 100);
     String line;
     try {
      try {
       while ((line = br.readLine()) != null) {
        System.out.println(line);
       }
      } catch (EOFException e) {
      }
      br.close();
     } catch (IOException e) {
      System.err.println("problem reading spawn output" + e.getMessage());
     }
     // returning from run kills the thread.
    }
   }.start();

   // Spawn thread to read output of spawned program -- Error Stream
   new Thread() {
    public void run() {
     this.setName("Process-ErrorStream");
     // hook into output from spawned program
     final InputStream is = process.getErrorStream();
     final InputStreamReader isr = new InputStreamReader(is);
     final BufferedReader br = new BufferedReader(isr, 100);
     String line;
     try {
      try {
       while ((line = br.readLine()) != null) {
        System.out.println(line);
       }
      } catch (EOFException e) {
      }
      br.close();
     } catch (IOException e) {
      System.err.println("problem reading spawn output" + e.getMessage());
     }
     // returning from run kills the thread.
    }
   }.start();

   // Wait for Ghost Script to complete.
   System.out.println("waiting for command to finish ....");
   exitVal = process.waitFor();
   System.out.println("exitValue: " + exitVal);

   processingComplete = true;
   process = null;
   System.out.println("PostConversion.run(): Done converting ");
   
  } catch (Exception e) {
   System.err.println("incomplete:"+ e);
  }
 }
 
 
 
 public static File convertToPS(File pdfFile) {
  String pdfFilePath = pdfFile.getAbsolutePath();
  String pdfFileName = pdfFile.getName();
  String psFileName = pdfFileName.substring(0, pdfFileName.indexOf(".pdf")) + ".ps";
  String psFilePath = "C:\\Temp\\"+ "\\" + psFileName;

  StringBuffer command = new StringBuffer("C:/Progra~1/gs/gs8.61/bin/gswin32c.exe");
  command.append(" -q -sOutputFile#");
  command.append("\"");
  command.append(psFilePath);
  command.append("\"");
  command.append(" -dNOPAUSE -dBATCH -dSAFER -sDEVICE=pswrite ");

  command.append("\"");
  command.append(pdfFilePath);
  command.append("\"");
  
  try {

   PostScriptConversion conversionThread = new PostScriptConversion(command.toString());
   conversionThread.setName("PostConversion");
   conversionThread.start();
   try {
    //Set timeout for PS conversion. Best is to externalize.. for testing read directly.
    Thread.sleep(100000);
   } catch (Exception ex) {
    ex.printStackTrace();
   }

   if (!conversionThread.processingComplete) {
    if (conversionThread.process != null) {
     File psFile = new File(psFilePath);
     if (psFile.exists()) {
      conversionThread.process.destroy();
      conversionThread.process = null;
      System.err.println("Killed process.");
     }
    }
   }

   System.out.println(" Return value from PS conversion " + conversionThread.exitVal);

   if (conversionThread.exitVal != 0) {
    return null;
   }

   return new File(psFilePath);
  } catch (Exception e) {
   System.err.println("Error converting to PS: " + psFilePath +" ExP:"+ e);
  }
  return null;
 }
 
 
 
 
 /**
  * 
  * This method adds Printer Job Language (PJL) as well as postscript commands to achieve desired finishing on the final output.
  * 
  * PJL commands will follow the structure
  * 
  * 1. PJL directive to indicate this is a PJL command stream 
  * 2. Any number of PJL commands there after  
  * 3. PJL command to set language to postscript eg: @PJL ENTER LANGUAGE = POSTSCRIPT or  @PJL ENTER LANGUAGE = PCLXL 
  * 4. the actual postscript with any additional  modifications 
  * 5. PJL directive to indicate the end of the PJL job
  * 
  * so basically we are adding PJL commands on top and embedding the postscrip output which is the 
  * original pdf  wrapped between PJL start and end Commands.
  * 
  * @param psFile
  * @throws IOException
  */
 public static void addAddtionalCommandsForPSFile(File psFile) throws IOException {

  File tempPSFile = new File(psFile.getPath() + "_upd");
  PrintWriter pw = null;
  FileReader fr = null;
  try {
   pw = new PrintWriter(tempPSFile);
   fr = new FileReader(psFile);

   // Begin the PJL job
   pw.println(PJL_START);
   //ANY PJL commands will go here 
   pw.println(PJL_COMMAND_TO_SELECT_PRINT_TRAY);
   //pw.println();  More PJL commands.
   //pw.println();  More PJL commands.
   //set the language back to postscript
   pw.println(PJL_POSTSCRIPT);
   
   //Now read inout Post script file to modify any commands on the fly as we read.
   LineNumberReader lnr = new LineNumberReader(fr);
   String line = lnr.readLine();
   pw.println(line); //copy  the first line from PS file...
   pw.println(POST_SCRIPT_COMMAND_FOR_DUPLEX_PRINTING); //Add Post Script command for duplex prining
   //pw.println(); //More PS commands (NOT PJL)
   
   line = lnr.readLine();
   int endPageFindCount = 0;
   int scaleFindCount = 0;
   while (line != null) {
    if (line.equalsIgnoreCase("%%EndPageSetup")) { //Count each page if you want...
     endPageFindCount++;
     pw.println("0 100 translate"); 
     pw.println(line);
    } else if (line.equalsIgnoreCase("0.1 0.1 scale")) {
     scaleFindCount++;
     pw.println("0.1 0.099 scale"); //Adjust scaling in Post script if you want. 
    } else {
     pw.println(line); //else just copy to the other file.
    }
    line = lnr.readLine();
   }

   // end the PJL job
   pw.println(PJL_END);

   System.out.println("Processing PS results: endPageFindCount: " + endPageFindCount + "  scaleFindCount: " + scaleFindCount);

  } catch (Exception e) {
   // TODO: handle exception
   System.err.println(e.getMessage()+":" +e);
  } finally {
   if (pw != null) {
    pw.close();
   }
   if (fr != null) {
    fr.close();
   }
  }

  FileUtils.forceDelete(psFile);
  FileUtils.copyFile(tempPSFile, psFile);
  FileUtils.forceDelete(tempPSFile);

 }
}

This above code should provide you enough  java code to print all print services on any given machine, modify a document to add any ghost script commands or PJL commands to control printer in a way at times we may not be able to do using Pure java code.

NOTE: Make sure your printer supports/installed with post script driver  else you may get "OFFENDING COMMAND" error when printing the document as you are sending post script commands to printer while your printer may be installed only with PCL driver (which can only understand PJL commands).

If using ghost script is not an option for your complex printing then try commercial java libraries (http://bfo.com/ ) which can do similar stuff for pdf printing.



3 comments:

  1. Hi , using your code i could generate the ps file from a pdf file. But when i am printing it prints
    ERROR:
    undefined
    OFFENDING COMMAND...............

    Do i need to set any printer properties also to accept the ps file and print the actual content of the pdf file ? Or any physical settings at the printer are required?

    Our requirement is to print many number of pdfs, duplex.

    ReplyDelete
  2. I have the same problem: OFFENDING COMMAND

    ReplyDelete
    Replies
    1. Does your printer support Post script ? Make sure your printer has post script driver installed.
      Some printers may not support POSTSCRIPT at all.
      In that case you may have to use PURe java solution. Check out http://bfo.com/. There license are cheaper too.

      Delete