/* JavaPrinterGraphics.java -- AWT printer rendering class.
   Copyright (C) 2006  Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

package gnu.java.awt.print;

import gnu.java.awt.peer.gtk.CairoSurface;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterGraphics;
import java.awt.print.PrinterJob;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.AttributedCharacterIterator;

/**
 * Graphics context to draw to PostScript.
 *
 * @author Sven de Marothy
 */
public class JavaPrinterGraphics extends Graphics implements PrinterGraphics
{

  /**
   * The used graphics context.
   */
  private Graphics g;

  /**
   * The associated printer job.
   */
  private PrinterJob printerJob;

  /**
   * Rendering resolution
   */
  private static final double DPI = 72.0;

  /**
   * Rendered image size.
   */
  private int xSize, ySize;

  /**
   * The image to render to.
   */
  private Image image;

  public JavaPrinterGraphics( PrinterJob printerJob )
  {
    this.printerJob = printerJob;
  }

  /**
   * Spool a document to PostScript.
   * If Pageable is non-null, it will print that, otherwise it will use
   * the supplied printable and pageFormat.
   */
  public SpooledDocument spoolPostScript(Printable printable, 
					 PageFormat pageFormat,
					 Pageable pageable)
    throws PrinterException
  {
    try 
      {
	// spool to a temporary file
	File temp = File.createTempFile("cpspool", ".ps");
	temp.deleteOnExit();
	
	PrintWriter out = new PrintWriter
	  (new BufferedWriter
	    (new OutputStreamWriter
	     (new FileOutputStream(temp), "ISO8859_1"), 1000000));

	writePSHeader(out);
	
	if(pageable != null)
	  {
	    for(int index = 0; index < pageable.getNumberOfPages(); index++)
	      spoolPage(out, pageable.getPrintable(index),
			pageable.getPageFormat(index), index);
	  }
	else
	  {
	    int index = 0;
	    while(spoolPage(out, printable, pageFormat, index++) ==
		  Printable.PAGE_EXISTS);
	  }
	 out.println("%%Trailer");
	 out.println("%%EOF");
	 out.close();
	 return new SpooledDocument( temp );
       } 
    catch (IOException e) 
      {
	PrinterException pe = new PrinterException();
	pe.initCause(e);
	throw pe;
      }
  }

  /**
   * Spools a single page, returns NO_SUCH_PAGE unsuccessful,
   * PAGE_EXISTS if it was.
   */
  public int spoolPage(PrintWriter out,
		       Printable printable, 
		       PageFormat pageFormat, 
		       int index) throws IOException, PrinterException
  {
    initImage( pageFormat );
    if(printable.print(this, pageFormat, index) == Printable.NO_SUCH_PAGE)
      return Printable.NO_SUCH_PAGE;
    g.dispose();
    g = null;
    writePage( out, pageFormat );
    return Printable.PAGE_EXISTS;
  }
  
  private void initImage(PageFormat pageFormat)
  {
    // Create a really big image and draw to that.
    xSize = (int)(DPI*pageFormat.getWidth()/72.0);
    ySize = (int)(DPI*pageFormat.getHeight()/72.0);
    
    // Swap X and Y sizes if it's a Landscape page.
    if( pageFormat.getOrientation() != PageFormat.PORTRAIT )
      {
	int t = xSize;
	xSize = ySize;
	ySize = t;
      }

    // FIXME: This should at least be BufferedImage. 
    // Fix once we have a working B.I.
    // Graphics2D should also be supported of course.
    image = CairoSurface.getBufferedImage(xSize, ySize);

    g = image.getGraphics();
    setColor(Color.white);
    fillRect(0, 0, xSize, ySize);
    setColor(Color.black);
  }

  private void writePSHeader(PrintWriter out)
  {
    out.println("%!PS-Adobe-3.0");      
    out.println("%%Title: "+printerJob.getJobName());
    out.println("%%Creator: GNU Classpath ");
    out.println("%%DocumentData: Clean8Bit");

    out.println("%%DocumentNeededResources: font Times-Roman Helvetica Courier");
    //    out.println("%%Pages: "+);  // FIXME # pages.
    out.println("%%EndComments");
    
    out.println("%%BeginProlog");
    out.println("%%EndProlog");
    out.println("%%BeginSetup");
    
    // FIXME: Paper name
    // E.g. "A4" "Letter"
    //    out.println("%%BeginFeature: *PageSize A4");
    
    out.println("%%EndFeature");

    out.println("%%EndSetup");
    
    //    out.println("%%Page: 1 1");
  }

  private void writePage(PrintWriter out, PageFormat pageFormat)
  {
    out.println("%%BeginPageSetup");

    Paper p = pageFormat.getPaper();
    double pWidth = p.getWidth();
    double pHeight = p.getHeight();

    if( pageFormat.getOrientation() == PageFormat.PORTRAIT )
      out.println( "%%Orientation: Portrait" );
    else
      {
	out.println( "%%Orientation: Landscape" );
	double t = pWidth;
	pWidth = pHeight;
	pHeight = t;
      }
      
    out.println("gsave % first save");
    
    // 595x842; 612x792 respectively
    out.println("<< /PageSize [" +pWidth + " "+pHeight+ "] >> setpagedevice");

    // invert the Y axis so that we get screen-like coordinates instead.
    AffineTransform pageTransform = new AffineTransform();
    if( pageFormat.getOrientation() == PageFormat.REVERSE_LANDSCAPE )
      {
	pageTransform.translate(pWidth, pHeight);
	pageTransform.scale(-1.0, -1.0);
      }
    concatCTM(out, pageTransform);
    out.println("%%EndPageSetup");

    out.println("gsave");


    // Draw the image
    out.println(xSize+" "+ySize+" 8 [1 0 0 -1 0 "+ySize+" ]"); 
    out.println("{currentfile 3 string readhexstring pop} bind");
    out.println("false 3 colorimage");
    int[] pixels = new int[xSize * ySize];
    PixelGrabber pg = new PixelGrabber(image, 0, 0, xSize, ySize, pixels, 0, xSize);

    try {
      pg.grabPixels();
    } catch (InterruptedException e) {
      out.println("% Bug getting pixels!");
    }

    int n = 0;
    for (int j = 0; j < ySize; j++) {
      for (int i = 0; i < xSize; i++) {
	out.print( colorTripleHex(pixels[j * xSize + i]) );
	if(((++n)%11) == 0) out.println();
      }
    }
    
    out.println();
    out.println("%%EOF");
    out.println("grestore");
    out.println("showpage");
  }
  
  /**
   * Get a nonsperated hex RGB triple, e.g. FFFFFF = white 
   */
  private String colorTripleHex(int num){
    String s = "";

    try {
      s = Integer.toHexString( ( num & 0x00FFFFFF ) );
      if( s.length() < 6 )
	{
	  s = "000000"+s;
	  return s.substring(s.length()-6);
	}
    } catch (Exception e){
      s = "FFFFFF";
    }

    return s;
  }

  private void concatCTM(PrintWriter out, AffineTransform Tx){
    double[] matrixElements = new double[6];
    Tx.getMatrix(matrixElements);
    
    out.print("[ ");
    for(int i=0;i<6;i++)
      out.print(matrixElements[i]+" ");
    out.println("] concat");
  }

  //-----------------------------------------------------------------------------
  /**
   * PrinterGraphics method - Returns the printer job associated with this object.
   */
  public PrinterJob getPrinterJob()
  {
    return printerJob;
  }

  /**
   * The rest of the methods here are just pass-throughs to g.
   */
  public void clearRect(int x, int y, int width, int height)
  {
    g.clearRect(x, y, width, height);
  }

  public void clipRect(int x, int y, int width, int height)
  {
    g.clipRect(x, y, width, height);
  }

  public void copyArea(int x, int y, int width, int height, int dx, int dy)
  {
    g.copyArea(x, y, width, height, dx, dy);
  }

  public Graphics create()
  {
    return g.create();
  }

  public void dispose()
  {
  }

  public void drawArc(int x, int y, int width, int height, int startAngle, 
		      int arcAngle)
  {
    g.drawArc(x, y, width, height, startAngle, arcAngle);
  }

  public boolean drawImage(Image img, int x, int y, Color bgcolor, 
			   ImageObserver observer)
  {
    return g.drawImage(img, x, y, bgcolor, observer);
  }

  public boolean drawImage(Image img, int x, int y, ImageObserver observer)
  {
    return g.drawImage(img, x, y, observer);
  }

  public boolean drawImage(Image img, int x, int y, int width, int height, 
			   Color bgcolor, ImageObserver observer)
  {
    return g.drawImage(img, x, y, width, height, bgcolor, observer);
  }

  public boolean drawImage(Image img, int x, int y, int width, int height, 
			   ImageObserver observer)
  {
    return g.drawImage(img, x, y, width, height, observer);
  }

  public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 
			   int sx1, int sy1, int sx2, int sy2, Color bgcolor, 
			   ImageObserver observer)
  {
    return g.drawImage(img, dx1,  dy1,  dx2,  dy2,  
		     sx1,  sy1,  sx2,  sy2, bgcolor, observer);
  }

  public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 
			   int sx1, int sy1, int sx2, int sy2, ImageObserver observer)
  {
    return g.drawImage(img, dx1,  dy1,  dx2,  dy2,  
		     sx1,  sy1,  sx2,  sy2, observer);
  }

  public void drawLine(int x1, int y1, int x2, int y2)
  {
    g.drawLine(x1, y1, x2, y2);
  }

  public void drawOval(int x, int y, int width, int height)
  {
    g.drawOval(x, y, width, height);
  }

  public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
  {
    g.drawPolygon(xPoints, yPoints, nPoints);
  }

  public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
  {
    g.drawPolyline(xPoints, yPoints, nPoints);
  }

  public void drawRoundRect(int x, int y, int width, int height, 
			    int arcWidth, int arcHeight)
  {
    g.drawRoundRect(x, y, width, height, arcWidth, arcHeight);
  }

  public void drawString(AttributedCharacterIterator iterator, int x, int y)
  {
    g.drawString(iterator, x, y);
  }

  public void drawString(String str, int x, int y)
  {
    g.drawString(str, x, y);
  }

  public void fillArc(int x, int y, int width, int height, 
		      int startAngle, int arcAngle)
  {
    g.fillArc(x, y, width, height, startAngle, arcAngle);
  }

  public void fillOval(int x, int y, int width, int height)
  {
    g.fillOval(x, y, width, height);
  }

  public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
  {
    g.fillPolygon(xPoints, yPoints, nPoints);
  }

  public void fillRect(int x, int y, int width, int height)
  {
    g.fillRect(x, y, width, height);
  }

  public void fillRoundRect(int x, int y, int width, int height, 
			    int arcWidth, int arcHeight)
  {
    g.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
  }

  public Shape getClip()
  {
    return g.getClip();
  }

  public Rectangle getClipBounds()
  {
    return g.getClipBounds();
  }

  public Color getColor()
  {
    return g.getColor();
  }

  public Font getFont()
  {
    return g.getFont();
  }

  public FontMetrics getFontMetrics(Font f)
  {
    return g.getFontMetrics(f);
  }

  public void setClip(int x, int y, int width, int height)
  {
    g.setClip(x, y, width, height);
  }

  public void setClip(Shape clip)
  {
    g.setClip(clip);
  }

  public void setColor(Color c)
  {
    g.setColor(c);
  }

  public void setFont(Font font)
  {
    g.setFont(font);
  }

  public void setPaintMode()
  {
    g.setPaintMode();
  }

  public void setXORMode(Color c1)
  {
    g.setXORMode(c1);
  }

  public void translate(int x, int y)
  {
    g.translate(x, y);
  }
}

