// -----------------------------------------------------------------------------
// tcIppPrintFormatter.java
//
// BeginPrologue
//
// UNIT NAME:           tcIppPrintFormatter
//
// COMPONENT NAME:      Print_Mgmt
//
// SEGMENT NAME:        CSSFS
//
// REVISION STATUS
//
// REV NUM     SCR NUM          AUTHOR                     DATE
// =============================================================================
//
// 1.0        SQQ_SCR           S.Alexander                SCR_DATE
// Description:
//      Initial Delivery.
//
// -----------------------------------------------------------------------------
// SYSTEM STATES AND MODES
//      All.
//
// INTERFACES
//      None.
//
// DATA FILES
//      None.
//
// CSCI QUALIFICATION TESTS SATISFIED
//      See the Software Requirements Trace Matrix (SRTM)
//
// EndPrologue
// -----------------------------------------------------------------------------
// PACKAGE
package cssfs.print_mgmt;

// JAVA API IMPORTS
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.awt.print.Printable;
import java.awt.print.Paper;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import javax.print.PrintException;
import javax.print.DocFlavor;
import javax.print.attribute.AttributeSet;
import javax.print.attribute.HashAttributeSet;
import javax.print.attribute.standard.Media;
import javax.print.attribute.standard.MediaSize;
import javax.print.attribute.standard.MediaSizeName;
import javax.print.attribute.standard.MediaPrintableArea;
import javax.print.attribute.standard.OrientationRequested;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import org.w3c.dom.Document;
import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.AttributesImpl;

// From FOP Extensions
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.fop.svg.SVGUtilities;
import org.apache.fop.apps.Driver;
import org.apache.fop.apps.StreamRenderer;

// IMPORTS
import cssfs.idd.tcCssLog;
import cssfs.print_mgmt.tcStreamMergeFilter;

// DESCRIPTION
/**
   <p>
   This class formats Graphics2D print data into a FOP document.
   The FOP document will be rendered into PDF by default.
   This class uses the Driver and FOTreeBuilder to render a PDF
   file.  The file is a temp file created in the default temp
   directory.  The file is deleted when the JVM exits.
   The formated PDF file is what should is sent to the printer.
   <p>
   DESIGN CONSIDERATIONS
    None.
**/
// -----------------------------------------------------------------------------
public class tcIppPrintFormatter 
{

   // ---------------------------- Constructor ---------------------------------
   /**
    * Initlializes this Formatter.
    */
   // --------------------------------------------------------------------------
   public tcIppPrintFormatter() throws Exception
   {
      // Create an set up a Blank Transformer to
      // transform from DOM to Stream objects with no XSL.
      TransformerFactory lcTransFactory = TransformerFactory.newInstance();
      mcTransformer = lcTransFactory.newTransformer();
      
      // Attributes for Block
      mcBlockAttrs = new AttributesImpl();
      mcBlockAttrs.addAttribute("", "break-before", "break-before", 
                                "CDATA", "page");
      
      // Attributes for SVG
      mcSvgAttrs = new AttributesImpl();
      mcSvgAttrs.addAttribute("", "xmlns", "xmlns", 
                              "CDATA", mcSvgNameSpace);
   }

   // ---------------------------- GetInputStream -----------------------------
   /**
    * Returns an InputStream to the Temp file created for the
    * Formatted PDF Document, that is ready for the printer.
    * @return a InputStream for reading the formatted print data.
    */
   // --------------------------------------------------------------------------
   public InputStream GetInputStream() throws IOException
   {
      FileInputStream lcFileIn = null;
      if(mcFile != null)
      {
         mcFileOut.flush();
         mcFileOut.close();
         lcFileIn = new FileInputStream(mcFile);
         mcFile.deleteOnExit();
      }
      return lcFileIn;
   }

   // ---------------------------- GetDocFlavor -------------------------------
   /**
    * Returns the DocFlavor that his formatter formats data into.
    * @return DocFlavor.INPUT_STREAM.PDF
    */
   // --------------------------------------------------------------------------
   public DocFlavor GetDocFlavor()
   {
      //return DocFlavor.INPUT_STREAM.POSTSCRIPT;
      //return DocFlavor.INPUT_STREAM.PDF;
      return DocFlavor.INPUT_STREAM.PCL;
   }

   // --------------------------- printPageable --------------------------------
   /**
    * This will format the Pageable into a PDF document using the
    * FOP API of PDFDocumentGraphics2D.
    * @param Pageable the Pageable Interface.
    * @param Attributes AttributeSet for the Print Job
    */
   // --------------------------------------------------------------------------
   public void printPageable(Pageable rcPageable, AttributeSet rcAttrSet)
      throws PrintException
   {
      // Check the number of pages.
      int lnNumOfPages = rcPageable.getNumberOfPages();
      if(lnNumOfPages == rcPageable.UNKNOWN_NUMBER_OF_PAGES)
         throw new PrintException("Can't Handle Unknown Number of Pages.");

      try
      {
         PageFormat lcFormat = initPageFormat(rcAttrSet);
         tcStreamMergeFilter lcStreamFilter = startFormat(lcFormat);
         
         // Loop over the pages.
         int lnPageExists = Printable.PAGE_EXISTS;
         for(int lnIndex = 0; 
             lnIndex < lnNumOfPages 
             && 
             lnPageExists != Printable.NO_SUCH_PAGE;
             lnIndex++)
         {
            // Get the PageFormat
            PageFormat lcPgFormat = rcPageable.getPageFormat(lnIndex);
            if(lcPgFormat == null)
               lcPgFormat = lcFormat;
            Printable lcPrintable = rcPageable.getPrintable(lnIndex);
            lnPageExists = formatPrintable(lcPrintable, 
                                           lnIndex,
                                           lcPgFormat,
                                           lcStreamFilter);
         }
         // Close the FOP Document.
         endFOPDocument(lcStreamFilter);
         lcStreamFilter.endMerger();
      }
      catch(Exception e)
      {
         throw new PrintException(e);
      }
   }
   
   // --------------------------- printPrintable -------------------------------
   /**
    * This will format the Printable into a PDF document using the
    * FOP API of PDFDocumentGraphics2D.
    * @param Printable the Printable Interface.
    * @param Attributes AttributeSet for the Print Job
    */
   // --------------------------------------------------------------------------
   public void printPrintable(Printable rcPrintable, AttributeSet rcAttrSet)
      throws PrintException
   {
      try
      {
         PageFormat lcFormat = initPageFormat(rcAttrSet);
         tcStreamMergeFilter lcStreamFilter = startFormat(lcFormat);
         
         // Loop over the pages.
         int lnPageExists = Printable.PAGE_EXISTS;
         int lnIndex = 0;
         while(lnPageExists != Printable.NO_SUCH_PAGE)
         {
            lnPageExists = formatPrintable(rcPrintable, 
                                           lnIndex++,
                                           lcFormat,
                                           lcStreamFilter);
         }
         // Close the FOP Document.
         endFOPDocument(lcStreamFilter);
         lcStreamFilter.endMerger();
      }
      catch(Exception e)
      {
         throw new PrintException(e);
      }
   }
   
   /** The Tmp File for the PDF Document.*/
   protected File mcFile = null;

   /** The FileOutputStream.*/
   protected FileOutputStream mcFileOut = null;
   
   /** 
    * Default Transformer used to convert the SVG Document
     *into FOP Commands/SAX Events.
   */
   protected Transformer mcTransformer = null;

   /** Attributes for Block.*/
   protected AttributesImpl mcBlockAttrs = null;
   
   /** Attributes for SVG. */
   protected AttributesImpl mcSvgAttrs = null;

   /** Element Names for the FOP Document.*/
   String mcSvgNameSpace = new String("http://www.w3.org/2000/svg");
   String mcFopNameSpace = new String("http://www.w3.org/1999/XSL/Format");
   String mcFo =                    new String("fo:");
   String mcRoot =                  new String("root");
   String mcPageSequence =          new String("page-sequence");
   String mcFlow =                  new String("flow");
   String mcBlock =                 new String("block");
   String mcInstreamForeignObject = new String("instream-foreign-object");

   // --------------------------- startFormat ----------------------------------
   /**
    * This method sets up the Formatter.
    * The FOP Document, Temp File will be initialized.
    * @param AttributeSet the Printing Request Attribute Set.
    * @param Format the default PageFormat.
    * @return the StreamMergeFilter that initialized the FOP Document.
    */
   // --------------------------------------------------------------------------
   protected tcStreamMergeFilter startFormat(PageFormat rcFormat) 
      throws Exception
   {
      // Create a temp file to store the PDF.
      FileOutputStream lcFileOut = initStream();
      
      // Create the FOP Driver
      Driver lcFopDriver = new Driver();
      lcFopDriver.setOutputStream(lcFileOut);
      lcFopDriver.setRenderer(Driver.RENDER_PCL);
      //lcFopDriver.setRenderer(Driver.RENDER_PDF);
      //lcFopDriver.setRenderer(Driver.RENDER_PS);

      // Create the StreamMergerFilter
      // This will merge the SVG documents into one
      // PDF file.
      SAXParserFactory lcSaxFactory = SAXParserFactory.newInstance();
      SAXParser lcSaxParser = lcSaxFactory.newSAXParser();
      tcStreamMergeFilter lcStreamFilter = new tcStreamMergeFilter(
         lcSaxParser.getXMLReader());
      
      // Set the FOTreeBuilder as a ContentHanlder
      lcStreamFilter.setContentHandler(lcFopDriver.getContentHandler());
      
      // Start Rendering
      lcStreamFilter.startMerger();
      
      // This intialization has to be after the call to startMerger().
      // Initialize the first part of the FOP tree
      // Then send it to the merger for PDF rendering.
      startFOPDocument(lcStreamFilter, rcFormat.getPaper());
      
      return lcStreamFilter;
   }

   // --------------------------- formatPrintable ------------------------------
   /**
    * This will format the Printable.
    * The formatted data will be stored in the Temp File, or Destination
    * location specified by the Attributes.
    * This method returns the Same integer as the Printable Interface.
    * @param Printable the Printable to format.
    * @param PageIndex the page being formated.
    * @param Format the Page's Format.
    * @param StreamFilter the ContentHandler to send FOP Commands to.
    * @return the integer returnd by the Printable.
    */
   // --------------------------------------------------------------------------
   protected int formatPrintable(Printable rcPrintable,
                                 int anPageIndex,
                                 PageFormat rcFormat,
                                 tcStreamMergeFilter rcStreamFilter) 
      throws Exception
   {
      tcCssLog lcLog = new tcCssLog(this, "formatPrintable");
      int lnPageExists = Printable.NO_SUCH_PAGE;
      
      // Initialize the Page width and height
      // Divide by 72 dpi to get Inches.
      Paper lcPaper = rcFormat.getPaper();
      float lnWidth = (float)lcPaper.getWidth()/72.0f;
      float lnHeight = (float)lcPaper.getHeight()/72.0f;

      // If Landscape need to switch width and height.
      if(rcFormat.getOrientation() == PageFormat.LANDSCAPE)
      {
         float lnTmp = lnWidth;
         lnWidth = lnHeight;
         lnHeight = lnTmp;
      }
      
      // Create the SVGDocument Factory
      Document lcSvgDoc = SVGUtilities.createSVGDocument(lnWidth, 
                                                         lnHeight);
      
      // Create the SVGGraphics2D to draw into the SVG Document.
      SVGGraphics2D lcSvgGraphics = new SVGGraphics2D(lcSvgDoc);
      
      // Get the Printable and pass the graphics for drawing.
      lnPageExists = rcPrintable.print(lcSvgGraphics,
                                       rcFormat, 
                                       anPageIndex);
      if(lnPageExists == Printable.PAGE_EXISTS)
      {
         // Master Reference for PageSequence
         AttributesImpl lcPsAttrs = new AttributesImpl();
         if(rcFormat.getOrientation() == PageFormat.LANDSCAPE)
         {
            lcPsAttrs.addAttribute("", "master-reference", 
                                   "master-reference", 
                                   "CDATA", "simple-landscape");
            lcLog.LogTest("PDF Landscape Paper Width:="
                          +lnWidth+" Height:="+lnHeight);
         }
         else
         {
            lcPsAttrs.addAttribute("", "master-reference", 
                                   "master-reference", 
                                   "CDATA", "simple");
            lcLog.LogTest("PDF Paper Width:="+lnWidth+" Height:="+lnHeight);
         }

         // Start page sequence
         rcStreamFilter.startElement(mcFopNameSpace, mcPageSequence, 
                                mcFo+mcPageSequence, lcPsAttrs);
         // Flow attribute name
         AttributesImpl lcFlowAttrs = new AttributesImpl();
         lcFlowAttrs.addAttribute("", "flow-name", "flow-name", 
                                  "CDATA", "xsl-region-body");
         
         // start flow
         rcStreamFilter.startElement(mcFopNameSpace, mcFlow,
                                mcFo+mcFlow, lcFlowAttrs);

         // Send FOP Commands/SAX Events through 
         // The StreamMergeFilter for PDF rendering.
         // This will create an fo:block and force a new page.
         rcStreamFilter.startElement(mcFopNameSpace, mcBlock, 
                                     mcFo+mcBlock, mcBlockAttrs);
         
         rcStreamFilter.startElement(mcFopNameSpace, 
                                     mcInstreamForeignObject, 
                                     mcFo+mcInstreamForeignObject, 
                                     new AttributesImpl());
                  
         // Set the Size of the SVG Image
         mcSvgAttrs.addAttribute("", "height", "height", 
                                 "CDATA", lnHeight+"in");
         mcSvgAttrs.addAttribute("", "width", "width", 
                                 "CDATA", lnWidth+"in");
         rcStreamFilter.startElement(mcSvgNameSpace, "svg", 
                                     "svg", mcSvgAttrs);
         
         // Transform the SVG Element into FOP Commands/SAX Events
         SAXResult lcResult = new SAXResult(rcStreamFilter);
         DOMSource lcSvgSource = new DOMSource(lcSvgGraphics.getRoot());
         mcTransformer.transform(lcSvgSource, lcResult);
         
         rcStreamFilter.endElement(mcSvgNameSpace, "svg", "svg");
         rcStreamFilter.endElement(mcFopNameSpace, 
                                   mcInstreamForeignObject, 
                                   mcFo+mcInstreamForeignObject);
         
         // End the fo:block
         rcStreamFilter.endElement(mcFopNameSpace, mcBlock, mcFo+mcBlock);
         // End Flow
         rcStreamFilter.endElement(mcFopNameSpace, mcFlow, mcFo+mcFlow);
         // End Page Sequence
         rcStreamFilter.endElement(mcFopNameSpace, mcPageSequence, 
                                   mcFo+mcPageSequence);
      }
      else
      {
         lcLog.LogDebug("Page does not exists Index:="+anPageIndex);
         // Send FOP Commands/SAX Events through 
         // This will send an empty fo:block element.
         // There needs to be atleast one inf the FOP document.
         // Also, the page break attribute is removed, therefore
         // it won't force an empty page.
         //rcStreamFilter.startElement(mcFopNameSpace, mcBlock, 
         //mcFo+mcBlock, new AttributesImpl());
      }
      return lnPageExists;
   }
   
   // --------------------------- initStream -----------------------------------
   /**
    * This will create a File in the Systems Temp Area to hold
    * the PDF document will it is being generated and rendered.
    * This temp file will be deleted on JVM exit.
    * If Destination not null, set File to the destination.
    * @param Attributes the PrintAttributeSet
    * @return a OutputStream that will output the rendered FOP Document.
    */
   // --------------------------------------------------------------------------
   protected FileOutputStream initStream() 
      throws Exception
   {
      mcFile = File.createTempFile("cssfs_print", ".pdf");
      mcFileOut = new FileOutputStream(mcFile);
      return mcFileOut;
   }

   // --------------------------- initPageFormat -------------------------------
   /**
    * Creates a PaageFormat object determined by the AttributeSet.
    * @param AttributeSet the Printing Request Attributes.
    * @return a PageFormat object initialized from the AttributeSet.
    */
   // --------------------------------------------------------------------------
   protected PageFormat initPageFormat(AttributeSet rcAttrSet)
   {
      HashAttributeSet lcAttrSet = new HashAttributeSet();
      if(rcAttrSet != null)
         lcAttrSet.addAll(rcAttrSet);
      
      // Determine the Media Size, Printable Area and Orientation
      Media lcMedia = (Media)lcAttrSet.get(Media.class);
      MediaSize lcMediaSize = MediaSize.NA.LETTER;
      MediaPrintableArea lcArea = (MediaPrintableArea)lcAttrSet.get(
         MediaPrintableArea.class);
      OrientationRequested lcOrient = (OrientationRequested)lcAttrSet.get(
         OrientationRequested.class);

      // If no Media, default the Media.
      if(lcMedia instanceof MediaSizeName)
      {
         lcMediaSize = MediaSize.getMediaSizeForName(
            (MediaSizeName)lcMedia);
      }

      // Initialize the PageFormat and Paper Size and ImageableArea
      // Paper expects the numbers in dpi or 72 dpi per inch.
      PageFormat lcFormat = new PageFormat();
      Paper lcPaper = new Paper();
      lcPaper.setSize(lcMediaSize.getX(MediaSize.INCH)*72.0,
                      lcMediaSize.getY(MediaSize.INCH)*72.0);
      if(lcArea == null)
      {
         // Default border to one inch.
         lcPaper.setImageableArea(72.0, 72.0, 
                                  lcPaper.getWidth()-144.0,
                                  lcPaper.getHeight()-144.0);
      }
      else
      {
         // set border to printable area
         double lnX = lcArea.getX(MediaSize.INCH)*72.0;
         double lnY = lcArea.getY(MediaSize.INCH)*72.0;
         lcPaper.setImageableArea(lnX, lnY,
                                  lcArea.getWidth(MediaSize.INCH)*72.0,
                                  lcArea.getHeight(MediaSize.INCH)*72.0);
      }
      lcFormat.setPaper(lcPaper);

      if(lcOrient == OrientationRequested.REVERSE_LANDSCAPE)
         lcFormat.setOrientation(PageFormat.REVERSE_LANDSCAPE);
      else if(lcOrient == OrientationRequested.LANDSCAPE)
         lcFormat.setOrientation(PageFormat.LANDSCAPE);

      return lcFormat;
   }

   // --------------------------- startFOPDocument -----------------------------
   /**
    * Initializes and sends FOP Commands/SAX Events to the StreamMergeFilter
    * which then Passes the Commands/SAX Events onto the FOTreeBuilder for
    * creating the FOP document.
    * @param Handler the ContentHandler to send FOP Commands to.
    * @param Paper the Paper object sizing the Rendered Page.
    */
   // --------------------------------------------------------------------------
   protected void startFOPDocument(ContentHandler rcHandler, Paper rcPaper) 
      throws Exception
   {
      // Initizlize the size
      // Paper's number are 72 dpi per inch
      double lnHeight = rcPaper.getHeight()/72.0;
      double lnWidth =  rcPaper.getWidth()/72.0;

      // Setup the FOP Document Commands/Elements.
      String lcSimplePageMaster = new String("simple-page-master");
      String lcLayoutMasterSet =  new String("layout-master-set");
      String lcRegionBody =       new String("region-body");

      // Setup the Default attributes for certian elements.
      AttributesImpl lcAttrs = new AttributesImpl();

      // Root attribute of FOP Namespace.
      AttributesImpl lcRootAttrs = new AttributesImpl();
      lcRootAttrs.addAttribute("", "xmlns:fo", "xmlns:fo", 
                               "CDATA", mcFopNameSpace);

      // Portrait Page Master Attributes.
      AttributesImpl lcPortraitAttrs = new AttributesImpl();
      lcPortraitAttrs.addAttribute("", "master-name", "master-name", 
                                   "CDATA", "simple");
      lcPortraitAttrs.addAttribute("", "page-height", "page-height", 
                                   "CDATA", lnHeight+"in");
      lcPortraitAttrs.addAttribute("", "page-width", "page-width", 
                                   "CDATA", lnWidth+"in");
      
      // Landscape Page Master Attributes.
      AttributesImpl lcLandscapeAttrs = new AttributesImpl();
      lcLandscapeAttrs.addAttribute("", "master-name", "master-name", 
                                    "CDATA", "simple-landscape");
      lcLandscapeAttrs.addAttribute("", "page-height", "page-height", 
                                    "CDATA", lnWidth+"in");
      lcLandscapeAttrs.addAttribute("", "page-width", "page-width", 
                                    "CDATA", lnHeight+"in");
      
      // Send the Commands/SAX Events
      // fo:root
      rcHandler.startElement(mcFopNameSpace, mcRoot,
                             mcFo+mcRoot, lcRootAttrs);
      // fo:layout-master-set
      rcHandler.startElement(mcFopNameSpace, lcLayoutMasterSet,  
                             mcFo+lcLayoutMasterSet,  lcAttrs);
      //simple-page-masters

      // Portrait
      rcHandler.startElement(mcFopNameSpace, lcSimplePageMaster, 
                             mcFo+lcSimplePageMaster, lcPortraitAttrs);
      rcHandler.startElement(mcFopNameSpace, lcRegionBody,
                             mcFo+lcRegionBody, lcAttrs);

      rcHandler.endElement(mcFopNameSpace, lcRegionBody, mcFo+lcRegionBody);
      rcHandler.endElement(mcFopNameSpace, lcSimplePageMaster, 
                           mcFo+lcSimplePageMaster);

      // Landscape
      rcHandler.startElement(mcFopNameSpace, lcSimplePageMaster, 
                             mcFo+lcSimplePageMaster, lcLandscapeAttrs);
      rcHandler.startElement(mcFopNameSpace, lcRegionBody,
                             mcFo+lcRegionBody, lcAttrs);

      rcHandler.endElement(mcFopNameSpace, lcRegionBody, mcFo+lcRegionBody);
      rcHandler.endElement(mcFopNameSpace, lcSimplePageMaster, 
                           mcFo+lcSimplePageMaster);

      rcHandler.endElement(mcFopNameSpace, lcLayoutMasterSet,
                           mcFo+lcLayoutMasterSet);
   }

   // --------------------------- endFOPDocument -------------------------------
   /**
    * Sends closing FOP Commands/SAX Events to the StreamMergeFilter
    * which then Passes the Commands/SAX Events onto the FOTreeBuilder for
    * creating the FOP document.
    * @param Handler the ContentHandler to send the FOP Commands to.
    */
   // --------------------------------------------------------------------------
   protected void endFOPDocument(ContentHandler rcHandler) 
      throws Exception
   {
      rcHandler.endElement(mcFopNameSpace, mcRoot, mcFo+mcRoot);
   }
}
