/* 
______________________________________________________________________
*
* Copyright (c) 1996-2004 QUEST SOFTWARE INC.  All Rights Reserved.
* http://java.quest.com
*
* This software is the confidential and proprietary information of
* Quest Software Inc. ("Confidential Information").  You shall not disclose
* such Confidential Information and shall use it only in accordance with the
* terms of the license agreement you entered into with Quest Software.
*
* QUEST SOFTWARE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
* OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. QUEST SOFTWARE SHALL NOT BE LIABLE FOR ANY
* DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
* ______________________________________________________________________
*/

// RCSID -- $RCSfile: Graphics2DPCL.java,v $ $Revision: 1.2 $ $Date: 2006-03-17 21:13:25 $ $Locker:  $  Quest Software Inc.
package com.klg.jclass.page.pcl;

import com.klg.jclass.page.EPSImage;
import com.klg.jclass.page.Graphics2DBase;
import com.klg.jclass.page.JCUnit;


import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.io.OutputStream;
/* JDK12_END */

/**
 * Overrides Graphics2D to provide the java interface
 * to postscript drawing.
 */

public class Graphics2DPCL extends Graphics2DBase implements Cloneable {

// base(x,y) stores the origin of the current context relative to the page
protected double    base_x = 0.0;
protected double    base_y = 0.0;

// Store the colour used to draw various components
protected Color currentTextColor = null;
protected Color currentLineColor = null;
protected Color currentFillColor = null;
protected boolean textColorChanged = true;
protected boolean lineColorChanged = true;
protected boolean fillColorChanged = true;

// The symbol set being used to print text
protected String symbolSet = new String("");

// printer is the printer that this Graphics writes to
protected JCPCLPrinter printer;

protected static double DECIPOINT 	= 10.0;
protected boolean landscapeMode = false;
protected boolean resetFont		= true;

protected static int greyscale[] = {
		0,  1,  1,  3,  3,  3,  3,  3,  3,  3,
		3,  11, 11, 11, 11, 11, 11, 11, 11, 11,
		11, 21, 21, 21, 21, 21, 21, 21, 21, 21,
		21, 21, 21, 21, 21, 21, 36, 36, 36, 36,
		36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
		36, 36, 36, 36, 36, 36, 56, 56, 56, 56,
		56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
		56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
		56, 81, 81, 81, 81, 81, 81, 81, 81, 81,
		81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
		100};

protected static final int	MOVE = 0;
protected static final int	INTERSECT = 1;
protected static final int	PUSH = 0;
protected static final int	REPLACE = 1;




/**
 *
 * Constructor
 * @deprecated Use Graphics2DPCL(OutputStream os, JCPCLPrinter printer) instead
 */
public Graphics2DPCL(OutputStream os) {
	super(os);
	this.printer = null;

	
	
}

public Graphics2DPCL(OutputStream os, JCPCLPrinter printer) {
	super(os);
	this.printer = printer;

	
	
}

/**
 * over-ride clone to keep stacks separate
 */
public Object clone() {
    Rectangle2D clip = (Rectangle2D)getClip();
	Graphics2DPCL		gc;

	try {
		gc = (Graphics2DPCL) super.clone();
	}
	catch (CloneNotSupportedException e) {
		return(null);
	}

	
	

	gc.transform = new AffineTransform(transform);
	gc.clip = (Rectangle2D) clip.clone();

	
	

	return((Object) gc);
}

/**
 * Overrides g2d method.
 */
public void drawString(String string, float x, float y) {

    // shouldn't attempt to draw null or empty strings
    if (string == null || string.equals("")) {
        return;
    }

	int		index 		= 0;
	int 	end_index 	= string.length();
	CharSetEntryPCL		char_data;
	CharSetEntryPCL		last_char = null;

	
	

	// Make sure we are in the correct state
	updateContext();

	// If the text is out of the clipping window, don't print it


	
	Rectangle2D bounds = getClipBounds();
	if (x < bounds.getX() || y < bounds.getY() ||
		x > (bounds.getX() + bounds.getWidth()) ||
		y > (bounds.getY() + bounds.getHeight()))
	{
		return;
	}


	// If we're in line-drawing mode, exit it
	if (isGL2Mode()) {
		exitGl2Mode();
	}

	// set the current color
	outputTextColor();

	// get the font
	FontPCL font = (FontPCL) getFont();

	// set the font if we must
	if (resetFont || currentFont == null || !currentFont.equals(font)) {
	 	outputFontDefinition(font);
		resetFont = false;
		currentFont = font;
	}

	// turn on underlining if the text is underlined
	if (underline) {
		output("\033&d0D"); 	// floating underline
	}

	// Compute the initial location and convert to decipoints
	x = (float) ((base_x + x) * DECIPOINT);
	y = (float) ((base_y + y) * DECIPOINT); // FIX - data.y_offset;
	

	// initialise the output buffer
	initializeOutString(x, y);

	/*
	 * copy characters from the input string, adjusting the position
	 * for kerning distances and rewriting special and non-printing
	 * characters
	 */
	FontMetricsPCL fontMetrics = new FontMetricsPCL(font);

	for (index = 0; index < end_index; index++) {
		char c = string.charAt(index);
		char_data = font.getCharMetric(c);

		// kern to the previous character
		if (last_char != null && last_char.getKerns().size() > 0) {

			Integer key = new Integer(char_data.getIndex());
			KernPairPCL kern = last_char.getKernPair(key);

			if (kern != null && kern.kernValue != 0) {
				// Print out the existing string
				printOutString();

				// Adjust position by kerning distance
				double deciWidth =
					fontMetrics.convertToDecipoints(
						kern.kernValue,font.getSize());
				out.x += deciWidth;
			}
		}

		// add current char to output buffer
		double width = fontMetrics.convertToDecipoints(
			char_data.metrics.getHorizontalEscapement(), font.getSize());
		appendToOutString(c, width);
		last_char = char_data; }

	// Print out the existing string
	// print_outstring(out, dos->landscape_mode);
	printOutString();

	// turn off underlining if it is on
	if (underline) {
		output("\033&d@");
	}

} // end drawString

/**
 * Override the base Graphics2D translate method
 * @param x The horizontal position to translate to
 * @param y The vertical position to translate to
 */
public void translate(int x, int y) {
	translate((double) x, (double) y);
}

/**
 * Override the base Graphics2D translate method
 * @param x The horizontal position to translate to
 * @param y The vertical position to translate to
 */
public void translate(double x, double y) {

    Rectangle2D clip = (Rectangle2D)getClip();

	
	
	

	double clipX, clipY, clipWidth, clipHeight;

	// Keep track of the total transformation of the graphics
	
	transform.translate(x, y);
	

	// Adjust the clip so it doesn't move
	clipX = clip.getX() - x;
	clipY = clip.getY() - y;
	clipWidth = clip.getWidth();
	clipHeight = clip.getHeight();
	clip.setRect(clipX, clipY, clipWidth, clipHeight);

	

	// Adjust the translation of the graphics
	translateInternal(x, y);
}

/**
 * Override the Graphics2D scale method
 * @param x the scaling factor for units along the X axis
 * @param y the scaling factor for units along the Y axis
 */
public void scale(double x, double y) {

    Rectangle2D clip = (Rectangle2D)getClip();

	double clipX, clipY, clipWidth, clipHeight;

	// Keep track of the total transformation of the graphics
	transform.scale(x, y);

	// Adjust the clip so it doesn't move
	clipX = clip.getX() / x;
	clipY = clip.getY() / y;
	clipWidth = clip.getWidth() / x;
	clipHeight = clip.getHeight() / y;
	clip.setRect(clipX, clipY, clipWidth, clipHeight);

	// Adjust the translation of the graphics
	// ??? scaleInternal(x, y, REPLACE);
}

/**
 * Restore the previous coordinate system
 */
protected void restoreTranslation() {

	
}

/**
 * Override the base Graphics2D clipRect method
 * @param x The left side of the clip rectangle
 * @param y The top edge's position of the clip rectangle
 * @param width The width of the clip rectangle
 * @param height The height of the clip rectangle
 */
public void clipRect(int x, int y, int width, int height) {

	this.clipRect((double) x, (double) y, (double) width, (double) height);
}

/**
 * Override the base Graphics2D clipRect method
 * @param x The left side of the clip rectangle
 * @param y The top edge's position of the clip rectangle
 * @param width The width of the clip rectangle
 * @param height The height of the clip rectangle
 */
public void clipRect(double x, double y, double width, double height) {

	
	setClipInternal(x, y, width, height, INTERSECT);
}

/**
 * Restore the previous coordinate system. Currently no-op
 */
protected void restoreClip() {

    Rectangle2D clip = (Rectangle2D)getClip();

	
	
}

/**
 * Set the current clipping path and output the necessary codes
 */
protected void setCurrentClip(Rectangle2D clip) {

	
	

	// Set the clip path if it is possible (in GL2 mode)
	if (isGL2Mode()) {
		double x1 = (clip.getX() + base_x) * DECIPOINT;
		double x2 = x1 + clip.getWidth() * DECIPOINT;
		double y2 = (clip.getY() + base_y) * DECIPOINT;
		double y1 = y2 + clip.getHeight() * DECIPOINT;
		x1 = adjustPoint(x1, y1, landscapeMode);
		x2 = adjustPoint(x2, y2, landscapeMode);

		

		output("IW" + truncateDecimalDigits(x1) + " " +
			   truncateDecimalDigits(y1) + " " + truncateDecimalDigits(x2) +
			   " " + truncateDecimalDigits(y2));
	}
	super.clip = clip;
}

//////////////////// Some Graphics (not 2D) methods /////////


/**
 *
 * Overrides graphics method
 *
 */
public void setFont(Font f) {

	// Make sure we are in the correct state
	updateContext();

	// need to be out of gl2 mode to set the font
	if (isGL2Mode()) {
		exitGl2Mode();
	}

	if (!(f instanceof FontPCL)) {
		// if an awt font is passed in, get its PCL equivalent
		f = FontPCL.getNativeFont(f);
	}

	// only set it if we must
	if (currentFont != null && currentFont.equals(f) && !resetFont) {
	 	return;
	}

	// only need reset when doc started
	resetFont = false;

	// set it in super class
	currentFont = f;
	fontChanged = true;

	// print the control codes to change the font
	outputFontDefinition(f);
}

private void outputFontDefinition(Font f) {

	FontPCL font 			= (FontPCL) f;
	TypeFacePCL typeface 	= font.getTypeface();

	// Set the symbol set
	if (!symbolSet.equals(typeface.symbol.latin1Set)) {
		output("\033(" + typeface.symbol.latin1Set);
		symbolSet = new String(typeface.symbol.latin1Set);
	}

	// Set proportional or not
	String fontSetting;
	if ((typeface.getStyle()& TypeFacePCL.PCL_TYPEFACE_FLAGS_FIXEDWIDTH) == 0) {
		fontSetting = new String("\033(s1p");
	}
	else {
		// For a fixed-width font we have to also set the horizontal spacing
		fontSetting = new String("\033(s0p" + font.getCharsPerInch() + "h");
	}

	// Set the size of the typeface
	fontSetting += font.getSize() + "v";

	// Set the style and weight of the typeface
	if ((typeface.getStyle() & TypeFacePCL.PCL_TYPEFACE_FLAGS_ITALIC) == 0) {
		fontSetting += "0s";
	}
	else {
		fontSetting += "1s";
	}

	if ((typeface.getStyle() & TypeFacePCL.PCL_TYPEFACE_FLAGS_BOLD) == 0) {
		fontSetting += "0b";
	}
	else {
		fontSetting += "3b";
	}

	// Select this typeface
	fontSetting += typeface.general.typefaceSelectionString + "T";
	output(fontSetting);
}



/**
 *
 * Overrides Graphics method
 *
 */
public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {

	// set up the line width and color line mode
	setupGraphics(false);

	// get the first point
	int x = xPoints[0];
	int y = yPoints[0];

	// translate to new origin and convert it to decipoints
	x = (int) ((base_x + x) * DECIPOINT);
	y = (int) ((base_y + y) * DECIPOINT);

	x = (int) adjustPoint(x, y, landscapeMode);

	// and move the pen to the first point
	output("PU" + x + "," + y + ";");

	// draw the rest of the points
	printPoly(xPoints, yPoints, nPoints);

	// this method copes with any necessary cleanup
	cleanupGraphics();
}


/**
 *
 * Overrides Graphics method
 *
 */
public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
	boolean fill = true;
	printPolygon(xPoints, yPoints, nPoints, fill);
}

public  FontMetrics getFontMetrics(Font font) {
	if (!(font instanceof FontPCL)) {
		// if an awt font is passed in, get its PCL equivalent
		font = FontPCL.getNativeFont(font);
	}
	return new FontMetricsPCL((FontPCL) font);
}

/**
 *
 * Overrides g2d method
 *
 */
public  void drawPolygon(int xPoints[], int yPoints[], int nPoints) {
	boolean fill = false;
	printPolygon(xPoints, yPoints, nPoints, fill);
}


/**
 *
 * Overrides g2d method
 *
 */
public boolean drawImage(Image img, int x, int y, int width, int height,
		Color bgcolor, ImageObserver observer) {

	// Make sure we are in the correct state
	updateContext();

	if (img instanceof EPSImage) {
		// eps images are not supported in pcl, so draw a rectangle instead
		Color c = getColor();
		setColor(Color.lightGray);
		fillRect(x, y, width, height);
		setColor(c);
		return true;
	}
	else {
		return drawJavaImage(img, x, y, width, height, bgcolor, observer);
	}
}


//////////////////////// Device-specific methods //////////////////////

/**
 * Enter a graphics context
 */
public void startContext() {
    Rectangle2D clip = (Rectangle2D)getClip();

	
	

	// Reset the font, colour, etc.
	resetFont = true;
	colorChanged = true;

	// translate to the start of the new context and clip to its bounds
	// setCurrentClip(this.clip);
	// setClipInternal(clip.getX(), clip.getY(), clip.getWidth(),
	// 				clip.getHeight(), MOVE, PUSH);
	// translateInternal(transform.getTranslateX(), transform.getTranslateY(),
	// 				  PUSH);
}

/**
 * Exit a graphics context
 */
public void endContext() {

	

	// this.restoreTranslation();
	// this.restoreClip();
}

/**
 * Add/adjust a clip rectangle to/on the clip stack
 */
protected void setClipInternal(double x, double y, double width, double height,
							   int intersectClip) {
    Rectangle2D clip = (Rectangle2D)getClip();

	
	

	

	// To clip to the intersection of the old and new clip rectangles,
	if (intersectClip == INTERSECT) {

		// Clip the origin of the new clip rectangle to the existing clip path
		if (clip.getX() > x) {
			width -= (clip.getX() - x);
			x = clip.getX();
		}
		if (clip.getY() > y) {
			height -= (clip.getY() - y);
			y = clip.getY();
		}

		// Clip the bounds of the given clip rectangle
		if (x + width > clip.getX() + clip.getWidth()) {
			width = clip.getX() + clip.getWidth() - x;
		}
		if (y + height > clip.getY() + clip.getHeight()) {
			height = clip.getY() + clip.getHeight() - y;
		}
	}

	

	// Set and store the new clipping bounds
	clip.setRect(x, y, width, height);

	// output the new bounds if this context is current
	if (isContextCurrent()) {
		setCurrentClip(clip);
	}
}

protected void translateInternal(double x, double y) {

	

	// Accumulate the new base point
	base_x += x;
	base_y += y;

	
}

/**
 *
 * Device-specific method
 *
 */
public void drawArcPW(double x, double y, double width, double height,
					  double startAngle, double arcAngle, int type) {

	double factor = 1;

	
	
	

	// Compute the semi-axes and determine if the arc is elliptical
	double semiMajorX = width / 2.0;
	double semiMajorY = height / 2.0;
	boolean elliptic = (width != height);

	// DEGENERATE CASE: if width or height are zero, just draw a line
	if (width == 0) {
		drawLinePW(x, y + semiMajorY, x, y - semiMajorY);
		return;
	}
	if (height == 0) {
		drawLinePW(x + semiMajorX, y, x - semiMajorX, y);
		return;
	}

	// set up the line width and color line mode
	setupGraphics(false);

	// Compute the centre of the arc
	// Note: the java origin is not the center of the arc's
	// circle as it is in postscript/pcl, it is the center of the
	// rectangle which bounds the arc
	x = (x + base_x + semiMajorX) * DECIPOINT;
	y = (y + base_y + semiMajorY) * DECIPOINT;
	semiMajorX = semiMajorX * DECIPOINT;
	semiMajorY = semiMajorY * DECIPOINT;

	x = adjustPoint(x, y, landscapeMode);

	// if drawing an ellipse, set anisotropic scaling (vert != horiz)
	if (elliptic) {

		// Transform the scale so the arc is circular
		factor = height / width;
		output("SC0,1.411111,0,-" + (factor * 1.411111) + ",2;");

		// Adjust the center point of the arc
		y = y / factor;
	}

	// If we're not drawing a full circle...
	if (Math.abs(arcAngle) < 360.0) {

		// Convert the start point of the arc ...
		// NOTE: we compute radians because 1.1 doesn't provide a conversion
		double rad = startAngle / 180.0 * Math.PI;
		double sX = x + semiMajorX * Math.cos(rad);
		double sY = y + semiMajorY * Math.sin(rad);

		// if this is an elliptic arc, we have to adjust the scaling
		if (elliptic) {
			sY = sY / factor;
		}

		// Draw the arc
		output("PU" + truncateDecimalDigits(sX) + "," +
			   truncateDecimalDigits(sY) + ";PD;AA" +
			   truncateDecimalDigits(x) + "," +
			   truncateDecimalDigits(y) + "," +
			   truncateDecimalDigits(arcAngle) + ";");
	}

	// otherwise the arc completes a full circle
	else {
		// draw the circle
		output("PU" + truncateDecimalDigits(x) + "," +
			   truncateDecimalDigits(y) + ";CI" +
			   truncateDecimalDigits(semiMajorX) + ";");
	}

	// correct the scaling from drawing an ellipse
	if (elliptic) {
		output("SC0,1.411111,0,-1.4111111,2");
	}

	// this method copes with any necessary cleanup
	cleanupGraphics();
}

/**
 *
 * Device-specific method
 *
 */
public void fillArcPW(double x, double y, double width, double height,
					  double startAngle, double arcAngle, int type) {

	
	
	

	// Compute the semi-axes and determine if the arc is elliptical
	double semiMajorX = width / 2.0;
	double semiMajorY = height / 2.0;
	boolean elliptic = (width != height);

	// if width or height are zero, just draw a line
	if (width == 0) {
		drawLinePW(x, y + semiMajorY, x, y - semiMajorY);
		return;
	}
	if (height == 0) {
		drawLinePW(x + semiMajorX, y, x - semiMajorX, y);
		return;
	}

	// set up the line width and color line mode
	setupGraphics(true);

	// Compute the centre of the arc
	// Note: the java origin is not the center of the arc's
	// circle as it is in postscript/pcl, it is the center of the
	// rectangle which bounds the arc
	x = (x + base_x + semiMajorX) * DECIPOINT;
	y = (y + base_y + semiMajorY) * DECIPOINT;
	semiMajorX = semiMajorX * DECIPOINT;
	semiMajorY = semiMajorY * DECIPOINT;

	x = adjustPoint(x, y, landscapeMode);

	// if drawing an ellipse, set anisotropic scaling (vert != horiz)
	if (elliptic) {

		// Transform the scale so the arc is circular
		double factor = height / width;
		output("SC0,1.411111,0,-" + (factor * 1.411111) + ",2;");

		// Adjust the center point of the arc
		y = y / factor;
	}

	// move the pen to the centre of the circle/wedge
	output("PU" + truncateDecimalDigits(x) + "," +
		   truncateDecimalDigits(y) + ";");

	// If the arc is not a complete circle
	if (Math.abs(arcAngle) < 360.0) {

		// Draw the arc
		output("WG" + semiMajorX + "," + (-startAngle) + "," +
			   (-arcAngle) + ";");
	}

	// otherwise we're drawing a circle so use a circle function
	// NOTE:  We could just use "WG" for this case too.
	else {
		output("PM0;CI" + semiMajorX + ";PM2;FP;");
	}

	// correct the scaling from drawing an ellipse
	if (elliptic) {
		output("SC0,1.411111,0,-1.4111111,2");
	}

	// this method copes with any necessary cleanup
	cleanupGraphics();
}

/**
 *
 * Device-specific method
 *
 */
public void fillRectPW(double x, double y, double width, double height) {

	
	
	

	// set up the line width and color line mode
	setupGraphics(true);

	// convert to decipoints
	x 		*= DECIPOINT;
	y 		*= DECIPOINT;
	width 	*= DECIPOINT;
	height 	*= DECIPOINT;

	// Convert the extents of the rectangle
	// compute_total_offset(parent, &base_x, &base_y);
	x = base_x * DECIPOINT + x;
	y = base_y * DECIPOINT + y;

	x = adjustPoint(x, y, landscapeMode);

	// Move the pen to the start of the box and draw it
	output("PU" + truncateDecimalDigits(x) + "," + truncateDecimalDigits(y) +
		   ";RR" + truncateDecimalDigits(width) + "," +
		   truncateDecimalDigits(height) + ";");

	// this method copes with any necessary cleanup
	cleanupGraphics();
}

/**
 *
 * Device-specific method
 *
 */
public void setClipPW(double x, double y, double width, double height) {
	
	setClipInternal(x, y, width, height, MOVE);
}

/**
 *
 * Device-specific method
 *
 */
public void drawRectPW(double x, double y, double width, double height) {

	
	
	

	// set up the line width and color line mode
	setupGraphics(false);

	// convert to decipoints
	x 		*= DECIPOINT;
	y 		*= DECIPOINT;
	width 	*= DECIPOINT;
	height 	*= DECIPOINT;

	// Convert the extents of the rectangle
	// compute_total_offset(parent, &base_x, &base_y);
	x = base_x * DECIPOINT + x;
	y = base_y * DECIPOINT + y;

	x = adjustPoint(x, y, landscapeMode);

	// Move the pen to the start of the box and draw it
	output("PU" + truncateDecimalDigits(x) + "," + truncateDecimalDigits(y) +
		   ";ER" + truncateDecimalDigits(width) + "," +
		   truncateDecimalDigits(height) + ";");

	// this method copes with any necessary cleanup
	cleanupGraphics();
}

/**
 *
 * Device-specific method
 *
 */
public void drawLinePW(double x1, double y1, double x2, double y2) {

	
	
	

	// set up the line width and color line mode
	setupGraphics(false);

	// compute the actual locations of the endpoints of the line
	x1 = (x1 + base_x) * DECIPOINT;
	y1 = (y1 + base_y) * DECIPOINT;
	x2 = (x2 + base_x) * DECIPOINT;
	y2 = (y2 + base_y) * DECIPOINT;

	x1 = adjustPoint(x1, y1, landscapeMode);
	x2 = adjustPoint(x2, y2, landscapeMode);

	// ??? PA applies to PU?
	output("PU" + truncateDecimalDigits(x1) + "," + truncateDecimalDigits(y1) +
		   ";PD" + truncateDecimalDigits(x2) + "," + truncateDecimalDigits(y2) +
		   ";");

	// this method copes with any necessary cleanup
	cleanupGraphics();
}

/**
 *
 * Device-specific method
 *
 */
public void drawRoundRectPW(double x, double y, double width,
		double height, double arcWidth, double arcHeight) {
	drawRRect(x, y, width, height, arcWidth, arcHeight, "EP;");
}

/**
 *
 * Device-specific method
 *
 */
public void fillRoundRectPW(double x, double y, double width, double height,
		double arcWidth, double arcHeight) {
	drawRRect(x, y, width, height, arcWidth, arcHeight, "FP;");
}

/**
 * Overridden to be sure returned object is an instance of Rectangle2D
 * @return super.getClip(), if it returns a Rectangle2D. Otherwise,
 *         it's getBounds2D().
 */
public Shape getClip() {
    Shape result;
    if (super.getClip() instanceof Rectangle2D) {
        result = super.getClip();
    }
    else {
        result = super.getClip().getBounds2D();
    }
    return result;
}


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
////////////////////              Miscellaneous               ////////////
//////////////////// Some non-Graphics2D non Graphics methods ////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////


protected void exitGl2Mode() {

	// DEPRECATE:  Handle (printer == null)
	if (printer != null) {
		printer.isGL2Mode = false;
	}

	output("\033%0A");
}

/**
 * Initialise the clip and translation
 * (push initial values on the stacks)
 */
protected void initialiseClip(Rectangle2D pageSize) {
    Rectangle2D clip = (Rectangle2D)getClip();

	
	

	// convert to hp units
	double x = pageSize.getX();
	double y = pageSize.getY();
	double width = pageSize.getWidth();
	double height = pageSize.getHeight();

	// There should be no translations applied to the page yet
	
	

	base_x = x;
	base_y = y;

	// Set the new clipping bounds
	clip.setRect(0.0, 0.0, width, height);
}

/**
 *
 * Append a character to the output buffer.  *
 *
 */
protected void appendToOutString(char code, double width) {
	out.text.append(code);

	// Have already converted the width before calling
	out.width += width;
}

/*
 * Offset the x co-ordinate from the left edge to account for the
 * PCL logical page edge not corresponding to the physical page
 */
protected double adjustPoint(double x, double y, boolean landscape) {
	return (double) x - 180.0;
}


/**
 *
 * Flush the buffer to the file.
 *
 */
protected void printOutString() {

	double x = out.x;
	double y = out.y;

	x = adjustPoint(x, y, landscapeMode);

	// write 'er out
	try {
		String str = new String("\033&a" + truncateDecimalDigits(x) + "h" +
								truncateDecimalDigits(y) + "V" + out.text);

		os.write(str.getBytes());
		os.write('\n');
	} catch (java.io.IOException e) {
		e.printStackTrace();
	}

	// reset the buffer
	out.x 		+= out.width; // is left as a running total
	out.width 	= 0;



	
	out.text.delete(0, out.text.length());

}

/**
 *
 * Set the current color for graphics primitives on the output
 * device.  A different command is used to set the drawing colour
 * for lines than is used for fill areas.
 * @param fill True if the color is used to fill an area
 */
protected void outputGraphicsColor(boolean fill) {

	// if the colour has changed, update all output colours
	if (colorChanged) {
		textColorChanged = true;
		lineColorChanged = true;
		fillColorChanged = true;
		colorChanged = false;
	}

	// set the color
	Color c = getColor();

	// in Fill mode, check if the fill colour has changed
	if (fill) {
		if (c.equals(currentFillColor) && !fillColorChanged) {
			return;
		}
		else {
			currentFillColor = c;
			fillColorChanged = false;
		}
	}
	// otherwise, check if the line colour has changed
	else {
		if (c.equals(currentLineColor) && !lineColorChanged) {
			return;
		}
		else {
			currentLineColor = c;
			lineColorChanged = false;
		}
	}

	// pcl colors are btw 0.0-1.0; java between 0-255
	double red 		= c.getRed()	/255.0;
	double green	= c.getGreen()	/255.0;
	double blue		= c.getBlue()	/255.0;

	// Use grey fills to emulate colour in PCL
	int grey = convertRGBToGrey(red, green, blue);

	if (fill) {
		output("FT10," + grey + ";");
	}
	else {
		if (grey == 100) {
			output("SV0;");
		}
		else {
			output("SV1," + grey + ";");
		}
	}
}

/**
 * Set the current color for drawing text
 */
protected void outputTextColor() {

	// if the colour has changed, update all output colours
	if (colorChanged) {
		textColorChanged = true;
		lineColorChanged = true;
		fillColorChanged = true;
		colorChanged = false;
	}

	// set the color
	Color c = getColor();

	// If the colour hasn't changed, do nothing
	if (c.equals(currentTextColor) && !textColorChanged) {
		return;
	}

	currentTextColor = c;
	textColorChanged = false;

	// pcl colors are btw 0.0-1.0; java between 0-255
	double red 		= c.getRed()	/255.0;
	double green	= c.getGreen()	/255.0;
	double blue		= c.getBlue()	/255.0;

	// Use grey fills to emulate colour in PCL
	int grey = convertRGBToGrey(red, green, blue);

	output("\033*c" + grey + "G");
}

protected int convertRGBToGrey(double red, double green, double blue) {
	int		shading;
	float	ntsc_grey;

	// Use the NTSC colour -> greyscale function, and map it to 100 levels
	ntsc_grey 	= (float) (0.3 * red + 0.59 * green + 0.11 * blue);
	shading 	= (int) (100 - Math.round(ntsc_grey * 100));

	// Return the discrete PCL shading level corresponding to the grey level
	return(greyscale[shading]);
}

protected void enterGl2Mode(Rectangle2D clip_path) {

	// DEPRECATE:  Handle (printer == null)
	if (printer != null) {
		printer.isGL2Mode = true;
	}

	output("\033%0B");
	output("IN;SP1;TR0;");

	// Transpose the HP GL/2 coordinate system (19-18)
	output("SC0,1.411111,0,-1.4111111,2");
	output("IR0,100,100,0");

	// Set the clipping
	setCurrentClip(clip_path);
}

protected void setLineStroke() {

	// if the stroke has not been changed, do nothing.
	if (!strokeChanged) {
		return;
	}

	// line stroke is set in terms of points (1/72 of inch) whereas
	// pcl's default units for lines are millimeters, so convert
	float widthInPoints = ((BasicStroke)currentStroke).getLineWidth();
	double widthInMM =
				JCUnit.getAsCentimeters(JCUnit.POINTS, widthInPoints) * 10;

	// Use the default mode to select a thin line
	if (widthInMM == 0) {
		output("PW;");
	}
	else {
		output("PW" + truncateDecimalDigits(widthInMM) + ";");
	}

	// set the dash-ness
	float dashArray[] 	= ((BasicStroke)currentStroke).getDashArray();
	float dashPhase 	= ((BasicStroke)currentStroke).getDashPhase();

	if (dashArray != null) {

		double patternLengthInMM =
					JCUnit.getAsCentimeters(JCUnit.POINTS, dashArray[0]) * 20;

		// line types between 1-8 (p. 22-34)
		// LT lineType[,pattern length[,mode;]] (mode 1 => use mm)
		// Only use first dash length for now since I
		// can't get the array of values to work : (
		output("LT2," + truncateDecimalDigits(patternLengthInMM) + ",1;");
		dashMode = true;
	}
	else if (dashMode) {
		// restore solid line, the default
		output("LT;");
		dashMode = false;
	}

	strokeChanged = false;
}

protected void traceRectanglePath(double x, double y, double width,
								  double height, double radius) {

	double x1, y1, x2, y2, x3, y3;

	// Compute the arc endpoints and edges of the rectangle
	x1 = x + radius;
	y1 = y + radius;
	y2 = y + height - radius;
	x2 = x + width - radius;
	y3 = y + height;
	x3 = x + width;

	// move to the start point of the first arc
	output("PU" + x1 + "," + y + ";");

	// Enter polygon mode and draw the first arc
	output("PM0;PD;AA" + x1 + "," + y1 + "," + "-90;");

	// draw the left side and second arc
	output("PD" + x + "," + y2 + ";" + "AA" + x1 + "," + y2 + ",-90;");

	// draw the bottom side and third arc */
	output("PD" + x2 + "," + y3 + ";" + "AA" + x2 + "," + y2 + ",-90;");

	// Draw the last arc, close and end the polygon */
	output("PD" + x3 + "," + y1 + ";" + "AA" + x2 + "," + y1 + ",-90;PM2;");
}

/**
 * Common code for drawing/filling round rectangles
 */
public void drawRRect(double x, double y, double width, double height,
		double arcWidth, double arcHeight, String strokeOrFill) {

	
	

	// if the arcs are degenerate, simply draw a box
	if (arcWidth <= 0 || arcHeight <= 0) {
		if (strokeOrFill.equals("EP")) {
			drawRectPW(x, y, width, height);
		}
		else {
			fillRectPW(x, y, width, height);
		}
	}

	// set up the line width and color line mode
	setupGraphics(strokeOrFill.equals("FP"));

	// Compute the extents of the rectangle and convert to decipoints
	x = (x + base_x) * DECIPOINT;
	y = (y + base_y) * DECIPOINT;
	width = width * DECIPOINT;
	height = height * DECIPOINT;

	x = adjustPoint(x, y, landscapeMode);

	// if the arcs are elliptical, scale the coordinates appropriately
	if (arcWidth != arcHeight) {
		double factor = arcHeight / arcWidth;
		output("SC0,1,0," + factor + ",2;");
		y /= factor;
		height /= factor;
	}

	// trace the path and fill it
	traceRectanglePath(x, y, width, height, arcWidth * DECIPOINT);

	// EP means stroke the rectangle; FP means fill it
	output(strokeOrFill);

	// restore the scaling if we've adjusted it
	if (arcWidth != arcHeight) {
		double factor = arcWidth / arcHeight;
		output("SC0,1,0," + factor + ",2;");
	}

	// this method copes with any necessary cleanup
	cleanupGraphics();
}

/**
 *
 * common code for writing out points
 *
 */
protected void printPoly(int xPoints[], int yPoints[], int nPoints) {

	for (int i = 1; i < nPoints; i++) {

		// get next point on the path
		int x = xPoints[i];
		int y = yPoints[i];

		// translate to current origin, convert to decipoints
		x = (int) ((base_x + x) * DECIPOINT);
		y = (int) ((base_y + y) * DECIPOINT);

		x = (int) adjustPoint(x, y, landscapeMode);

		// Draw the path segment (this is not optimally efficient)
		output("PD" + x + "," + y + ";");
	}
}

protected void printPolygon(int[] xPoints, int[] yPoints, int nPoints,
							boolean fill) {

	// set up the line width and color line mode
	setupGraphics(fill);

	// get the first point on the path
	int x = xPoints[0];
	int y = yPoints[0];

	// translate to current origin, convert to decipoints
	x = (int) ((base_x + x) * DECIPOINT);
	y = (int) ((base_y + y) * DECIPOINT);

	x = (int) adjustPoint(x, y, landscapeMode);

	// Move the pen to the first point and enter polygon mode
	output("PU" + x + "," + y + ";" + "\nPM0;");

	// draw the rest of the points
	printPoly(xPoints, yPoints, nPoints);

	// Close the polygon, exit polygon mode and fill the polygon
	if (fill) {
		if (getWindingRule() == GeneralPath.WIND_EVEN_ODD) {
			output("PM2;\nFP;");
		}
		else {
			// WIND_NON_ZERO
			output("PM2;\nFP1;");
		}
	}
	else {
		output("PM2;\nEP;");
	}

	// this method copes with any necessary cleanup
	cleanupGraphics();
}

/**
 * Called by various methods to set the line width, current color,
 * and enter line drawing mode.
 * @param fill True if the graphical object is being filled (not stroked)
 */
protected void setupGraphics(boolean fill) {

	// Make sure we are in the correct state
	if (!isContextCurrent()) {
		updateContext();
	}

	// If we're not in line-drawing mode, enter it
	if (!isGL2Mode()) {
		Rectangle2D r;



		
		r = getClipBounds();


		enterGl2Mode(r);
	}

	// do line
	if (!fill) {
		setLineStroke();
	}

	// do color
	outputGraphicsColor(fill);
}

/**
 * This method copes with the (printer == null) case by always exiting
 * GL2 mode after any drawing operation.  (Much more expensive.)
 */
protected void cleanupGraphics() {
	if (printer == null) {
		exitGl2Mode();
	}
}

protected void terminateRun(int j, int runLength, int i) {

	output("PU" + (j - runLength) + "," + i + ";RR" + runLength + ",1;");
}

/**
 *
 * Draw normalized jpeg/gif image, that is, pixels which encode rgb info.
 * Note that since we allow pcl to do its own scaling, the width
 * and height parameters represent the DESIRED size of the image, not its
 * actual size.
 *
 */
protected boolean drawJavaImage(Image img, int x, int y, int width, int height,
		Color bgcolor, ImageObserver observer) {

    int imgWidth = img.getWidth(null);
    int imgHeight = img.getHeight(null);

	// get the pixels from the image
	int[] pixels = new int[imgWidth * imgHeight];

	PixelGrabber pg =
		new PixelGrabber(img, 0, 0, imgWidth, imgHeight, pixels, 0, imgWidth);
	try {
		pg.grabPixels();
	} catch (InterruptedException e) {
		System.err.println("interrupted waiting for pixels!");
		return false;
	}
	if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
		System.err.println("image fetch aborted or errored");
		return false;
	}

	// If we're in line-drawing mode, enter PCL mode
	if (isGL2Mode()) {
		exitGl2Mode();
	}

	// translate to current origin and convert to decipoints
	x = (int) ((base_x + x) * DECIPOINT);
	y = (int) ((base_y + y) * DECIPOINT);
	width = (int) (width * DECIPOINT);
	height = (int) (height * DECIPOINT);

	// create a PCL picture frame the size (18-10,11)
	output("\033*c" + width + "x" + height + "Y");

	// set the current point at the position of the image (6-6,11)
	x = (int) adjustPoint(x, y, landscapeMode);
	output("\033*p" + x + "x" + y + "Y");

	// attach the picture frame anchor (origin) to the current point (18-12)
	output("\033*c0T");

	// Switch into HP GL/2 mode
	output("\033%0B");
	output("IN;SP1;TR0;");

	// set the scaling factor of the HP GL/2 frame (19-45)
	output("SC0," + pg.getWidth() + ",0," + pg.getHeight() + ";");

	// swap the co-ordinates so that the origin is in the upper left (19-29)
	output("IR0,100,100,0;");

	int oldGrey = -2;
	int runLength;

	// For each pixel in the image draw a box representing the pixel
	int w = pg.getWidth();
	int h = pg.getHeight();
	for (int i = 0; i < h; i++) {

		runLength = 0;
		int j;
		for (j = 0; j < w; j++) {

			// get the next pixel
			int pixel = pixels[i * w + j];

			// break out its colors
			int red   = (pixel >> 16) & 0xff;
			int green = (pixel >>  8) & 0xff;
			int blue  = (pixel      ) & 0xff;

			// expects colors btw 0-1, not 0-255
			int grey = convertRGBToGrey(red/255.0, green/255.0, blue/255.0);

			// Ignore transparent pixels (index == -1)
			if (grey == -1) {

				// terminate an existing run
				if (runLength > 0) {
					terminateRun(j, runLength, i);
					runLength = 0;
				}

				continue;
			}

			// If the colour has changed, adjust the fill
			if (grey != oldGrey) {

				// terminate an existing run
				if (runLength > 0) {
					terminateRun(j, runLength, i);
					runLength = 0;
				}

				// change the colour
				StringBuffer cc = new StringBuffer("FT10,");
				cc.append(grey);
				output(cc.toString());

				oldGrey = grey;
			}

			runLength++;
		}

		// terminate an existing run
		if (runLength > 0) {
			terminateRun(j, runLength, i);
		}
	}

	// Switch back out of HP GL/2 mode
	output("\033%0A");

	// Re-attach the HP GL/2 picture frame to the page origin (19-18)
	StringBuffer po = new StringBuffer();
	po.append("\033*p0x0Y\n\033*c");
	// PENDING po.append((int) printer.pageFormat.getImageableX());
	double frame_width = ((JCPCLPrinter) printer).currentPageSize.getX() +
						 ((JCPCLPrinter) printer).currentPageSize.getWidth();
	po.append((int) (frame_width * DECIPOINT));
	po.append("x");
	// PENDING po.append((int) printer.pageFormat.getImageableY());
	double frame_height = ((JCPCLPrinter) printer).currentPageSize.getY() +
						  ((JCPCLPrinter) printer).currentPageSize.getHeight();
	po.append((int) (frame_height * DECIPOINT));
	po.append("y0T");
	output(po.toString());

	return true;

}

// NOTE:  This will always say "no" to GL2 mode if there is no printer;
//		  in conjunction with code that automatically exits GL2 mode
//		  after a drawing operation, this will ensure that any manually-
//		  created printer using the deprecated constructor leaves the
//		  printer in a known state

protected boolean isGL2Mode() {
	return(printer != null && printer.isGL2Mode);
}

}// end class
