/* -*- tab-width: 4 -*-
 * Electric(tm) VLSI Design System
 *
 * File: PostScriptColor.java
 * Input/output tool: PostScript color merged output
 * Written by: David Harris, 4/20/01 (David_Harris@hmc.edu)
 * Translated to Java by Steven Rubin: 12/05
 *
 * Copyright (c) 2005, Static Free Software. All rights reserved.
 *
 * Electric(tm) 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 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.io.output;

import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.EditWindow0;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.io.output.PostScript.PostScriptPreferences;
import com.sun.electric.tool.user.User;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.Orientation;

import java.awt.Color;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class writes files in PostScript format.
 * It handles color better than existing freely available
 * postscript generators.  It does not handle arbitrary rotations.
 *
 * Limitations:
 *  the code to handle quad trees is rather messy now
 *
 * Ideas:
 * 	center port labels
 * 	give options about aspect ratio / page size
 * 	put date on caption
 * 	print layers on caption
 * 	draw outlines around edges
 * 	handle black & white mode
 *
 * Things still to do:
 *    circles and arcs
 *    rotation of the plot
 *    encapsulated PostScript
 */
public class PostScriptColor
{
	// Constants
	private static final int    MAXLAYERS = 1000;
	private static final int    TREETHRESHOLD = 500;
	private static final double SMALL_NUM = 0.000001;
	private static final String FONTNAME = "Helvetica";

	private static class PsPoly
	{
		double [] coords;
		int       layer;
	};

	private static class PsBox
	{
		double [] pos = new double[4];  // 0: dx, 1: dy, 2: left, 3: bot
		int       layer;
		boolean   visible;
	};

	private static class PsLabel
	{
		String         label;
		double []      pos = new double[4];
		Poly.Type      style;
		TextDescriptor descript;
	};

	private static class PsCell
	{
		List<PsBox>      boxes;
		List<PsPoly>     polys;
		List<PsLabel>    labels;
		List<PsCellInst> inst;
	};

	private static class PsCellInst
	{
		double []  transform = new double[9];
		PsCell     inst;
	};

	private static class PsBoxElement
	{
		double [] pos = new double[4]; // 0: dx, 1: dy, 2: left, 3: bot
		int       layer;
		boolean   visible;
	};

	private static class PxBoxQuadTree
	{
		int             numBoxes;
		PsBoxElement [] boxes;
		double []       bounds = new double[4]; // 0: dx, 1: dy, 2: left, 3: bot
		PxBoxQuadTree   tl;
		PxBoxQuadTree   tr;
		PxBoxQuadTree   bl;
		PxBoxQuadTree   br;
		PxBoxQuadTree   parent;
		int             level;
	};

	private static class LayerMap
	{
		Layer      layer;
		int        mix1, mix2; // layer made from mix of previous layers, or -1 if not
		double     r, g, b;
		double     opacity;
		boolean    foreground;
	};

	// Globals
	private double []            psBoundaries = new double[4];
	private List<PsCell>         allCells;
	private LayerMap  []         allLayers = new LayerMap[MAXLAYERS];
	private List<ArrayList<PsBox>>flattenedBoxes = new ArrayList<ArrayList<PsBox>>();
	private PxBoxQuadTree []     quadTrees = new PxBoxQuadTree[MAXLAYERS];
	private List<ArrayList<PsPoly>>flattenedPolys = new ArrayList<ArrayList<PsPoly>>();
	private int                  numLayers;
//	private int                  totalBoxes = 0;
	private int                  totalCells = 0;
//	private int                  totalPolys = 0;
//	private int                  totalInstances = 0;
	private boolean              curveWarning;
	private Set<Technology>      techsSetup;
	private Map<Cell,PsCell>     cellStructs;
	private PostScript           psObject;

	private PostScriptColor(PostScript psObject)
	{
		this.psObject = psObject;
	}

	/**
	 * Main entry point for color PostScript output.
	 * @param psObject the PostScript writer object.
	 * @param prefs the PostScriptPreferences for this output.
	 * @param cell the Cell being written.
	 * @param pageWid the paper width (in 1/75 of an inch).
	 * @param pageHei the paper height (in 1/75 of an inch).
	 * @param pageMargin the inset margins (in 1/75 of an inch).
	 */
	public static void psColorPlot(PostScript psObject, PostScriptPreferences prefs, Cell cell,
		double pageWid, double pageHei, double pageMargin)
	{
		PostScriptColor psc = new PostScriptColor(psObject);
		psc.doPrinting(cell, prefs, pageWid, pageHei, pageMargin);
	}

	private void doPrinting(Cell cell, PostScriptPreferences prefs, double pageWid, double pageHei, double pageMargin)
	{
		totalCells = /* totalPolys = totalBoxes = totalInstances = */ 0;
		psBoundaries[0] = 1<<30;
		psBoundaries[1] = 1<<30;
		psBoundaries[2] = -1<<30;
		psBoundaries[3] = -1<<30;

		for (int i=0; i<MAXLAYERS; i++)
			quadTrees[i] = null;

		// initialize layer maps for the current technology
		numLayers = 0;
		techsSetup = new HashSet<Technology>();

		// mark all cells as "not written"
		cellStructs = new HashMap<Cell,PsCell>();
		getLayerMap(Technology.getCurrent());

		curveWarning = false;
		allCells = new ArrayList<PsCell>();
		extractDatabase(cell, prefs);
		mergeBoxes();
		flatten();
		genOverlapShapesAfterFlattening();
		writePS(cell, prefs.printForPlotter, pageWid, pageHei, pageMargin, prefs.wnd);
//		printStatistics();
	}

	/**
	 * Method to get the print colors and load them into the layer map.
	 */
	private void getLayerMap(Technology tech)
	{
		// see if this technology has already been done
		if (techsSetup.contains(tech)) return;
		techsSetup.add(tech);

		// read layer map
		int startLayer = numLayers;
		List<Layer> layerSorts = new ArrayList<Layer>();
		List<Layer> layerSorts1 = new ArrayList<Layer>();
		for(int i=0; i<tech.getNumLayers(); i++)
		{
			Layer layer = tech.getLayer(i);
			layerSorts.add(layer);
			layerSorts1.add(layer);
		}

		// sort by layer height
		Layer.getLayersSortedByRule(layerSorts1, Layer.LayerSortingType.ByDepth);

		// load the layer information
		for(int i=0; i<layerSorts.size(); i++)
		{
			if (numLayers >= MAXLAYERS)
			{
				System.out.println("More than " + MAXLAYERS + " layers");
				break;
			}
			Layer layer = layerSorts.get(i);
			allLayers[numLayers] = new LayerMap();
			allLayers[numLayers].layer = layer;
			EGraphics graph = psObject.localPrefs.gp.getGraphics(layer);
			allLayers[numLayers].opacity = graph.getOpacity();
			allLayers[numLayers].foreground = graph.getForeground();
			Color col = graph.getColor();
			allLayers[numLayers].r = col.getRed() / 255.0;
			allLayers[numLayers].g = col.getGreen() / 255.0;
			allLayers[numLayers].b = col.getBlue() / 255.0;
			allLayers[numLayers].mix1 = -1;
			allLayers[numLayers].mix2 = -1;
			if (allLayers[numLayers].opacity < 1)
			{
				// create new layers to provide transparency
				int curLayer = numLayers;
				for (int k=startLayer; k < curLayer; k++)
				{
					if (allLayers[k].foreground)
					{
						allLayers[++numLayers] = new LayerMap();
						allLayers[numLayers].opacity = 1;
						allLayers[numLayers].layer = null;
						allLayers[numLayers].foreground = true;
						allLayers[numLayers].r = allLayers[curLayer].r*allLayers[curLayer].opacity +
							allLayers[k].r*(1-allLayers[curLayer].opacity);
						allLayers[numLayers].g = allLayers[curLayer].g*allLayers[curLayer].opacity +
							allLayers[k].g*(1-allLayers[curLayer].opacity);
						allLayers[numLayers].b = allLayers[curLayer].b*allLayers[curLayer].opacity +
							allLayers[k].b*(1-allLayers[curLayer].opacity);
						allLayers[numLayers].mix1 = k;
						allLayers[numLayers].mix2 = curLayer;
					}
				}
			}
			numLayers++;
		}
	}

	private void extractDatabase(Cell cell, PostScriptPreferences prefs)
	{
		// check for subcells that haven't been written yet
		for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			if (!ni.isCellInstance()) continue;
			if (!prefs.expansionState.isExpanded(ni)) continue;
			Cell subCell = (Cell)ni.getProto();
			if (cellStructs.get(subCell) != null) continue;
			extractDatabase(subCell, prefs);
		}

		// create a cell
		PsCell curCell = new PsCell();
		curCell.boxes = new ArrayList<PsBox>();
		curCell.polys = new ArrayList<PsPoly>();
		curCell.labels = new ArrayList<PsLabel>();
		curCell.inst = new ArrayList<PsCellInst>();
		totalCells++;

		// add to the lists
		allCells.add(curCell);
		cellStructs.put(cell, curCell);

		// examine all nodes in the cell
		for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			NodeProto np = ni.getProto();
			if (ni.isCellInstance())
			{
				// instance
				if (!prefs.expansionState.isExpanded(ni))
				{
					// look for a black layer
					int i = 0;
					for( ; i<numLayers; i++)
						if (allLayers[i].r == 0 && allLayers[i].g == 0 &&
								allLayers[i].b == 0 && allLayers[i].opacity == 1) break;
					if (i < numLayers)
					{
						// draw a box by plotting 4 lines
						Rectangle2D niBounds = ni.getBounds();
						PsBox curBox = new PsBox();
						curBox.layer = i;
						curBox.visible = true;
						curBox.pos[0] = 1;
						curBox.pos[1] = niBounds.getHeight();
						curBox.pos[2] = niBounds.getMinX();
						curBox.pos[3] = niBounds.getMinY();
						curCell.boxes.add(curBox);

						curBox = new PsBox();
						curBox.layer = i;
						curBox.visible = true;
						curBox.pos[0] = niBounds.getWidth();
						curBox.pos[1] = 1;
						curBox.pos[2] = niBounds.getMinX();
						curBox.pos[3] = niBounds.getMinY();
						curCell.boxes.add(curBox);

						curBox = new PsBox();
						curBox.layer = i;
						curBox.visible = true;
						curBox.pos[0] = 1;
						curBox.pos[1] = niBounds.getHeight();
						curBox.pos[2] = niBounds.getMaxX();
						curBox.pos[3] = niBounds.getMinY();
						curCell.boxes.add(curBox);

						curBox = new PsBox();
						curBox.layer = i;
						curBox.visible = true;
						curBox.pos[0] = niBounds.getWidth();
						curBox.pos[1] = 1;
						curBox.pos[2] = niBounds.getMinX();
						curBox.pos[3] = niBounds.getMaxY();
						curCell.boxes.add(curBox);

						// add the cell name
						PsLabel curLabel = new PsLabel();
						curLabel.label = ni.getProto().describe(false);
						curLabel.pos[0] = niBounds.getMinX();
						curLabel.pos[1] = niBounds.getMaxX();
						curLabel.pos[2] = niBounds.getMinY();
						curLabel.pos[3] = niBounds.getMaxY();
						curLabel.style = Poly.Type.TEXTBOX;
						curLabel.descript = ni.getTextDescriptor(NodeInst.NODE_NAME);
						curCell.labels.add(curLabel);
					}
				} else
				{
					// expanded instance: make the invocation
					Cell npCell = (Cell)np;
					PsCell subCell = cellStructs.get(npCell);
					PsCellInst curInst = new PsCellInst();
					curInst.inst = subCell;
					newIdentityMatrix(curInst.transform);

					// account for instance position
					double [] transT = new double[9];
					newIdentityMatrix(transT);
					Rectangle2D cellBounds = npCell.getBounds();
					transT[2*3+0] -= cellBounds.getCenterX();
					transT[2*3+1] -= cellBounds.getCenterY();
					matrixMul(curInst.transform, curInst.transform, transT);

					// account for instance rotation
					double [] transR = new double[9];
					newIdentityMatrix(transR);
					Orientation o = ni.getOrient();
					double rotation = o.getCAngle() / 1800.0 * Math.PI;
					transR[0*3+0] = Math.cos(rotation); if (Math.abs(transR[0]) < SMALL_NUM) transR[0] = 0;
					transR[0*3+1] = Math.sin(rotation); if (Math.abs(transR[1]) < SMALL_NUM) transR[1] = 0;
					transR[1*3+0] = -Math.sin(rotation); if (Math.abs(transR[3]) < SMALL_NUM) transR[3] = 0;
					transR[1*3+1] = Math.cos(rotation); if (Math.abs(transR[4]) < SMALL_NUM) transR[4] = 0;

					matrixMul(curInst.transform, curInst.transform, transR);

					// account for instance transposition
					if (o.isCTranspose())
					{
						newIdentityMatrix(transR);
						transR[0*3+0] = 0;
						transR[1*3+1] = 0;
						transR[0*3+1] = -1;
						transR[1*3+0] = -1;
						matrixMul(curInst.transform, curInst.transform, transR);
					}

					// account for instance location
					newIdentityMatrix(transT);
					transT[2*3+0] = ni.getAnchorCenterX();
					transT[2*3+1] = ni.getAnchorCenterY();
					matrixMul(curInst.transform, curInst.transform, transT);
					curCell.inst.add(curInst);
				}
			} else
			{
				// primitive: generate its layers
				FixpTransform trans = ni.rotateOut();
				Poly [] polys = np.getTechnology().getShapeOfNode(ni);
				for(int i=0; i<polys.length; i++)
				{
					Poly poly = polys[i];
					poly.transform(trans);
					plotPolygon(poly, curCell);
				}
			}
		}

		// add geometry for all arcs
		for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
		{
			ArcInst ai = it.next();
			Poly [] polys = ai.getProto().getTechnology().getShapeOfArc(ai);
			for(int i=0; i<polys.length; i++)
			{
				Poly poly = polys[i];
				plotPolygon(poly, curCell);
			}
		}

		// add the name of all exports
		for(Iterator<Export> it = cell.getExports(); it.hasNext(); )
		{
			Export pp = it.next();
			PsLabel curLabel = new PsLabel();
			curLabel.label = pp.getName();
			Rectangle2D bounds = pp.getPoly().getBounds2D();
			curLabel.pos[0] = curLabel.pos[1] = bounds.getCenterX();
			curLabel.pos[2] = curLabel.pos[3] = bounds.getCenterY();
			curLabel.style = Poly.Type.TEXTCENT;
			curLabel.descript = pp.getTextDescriptor(Export.EXPORT_NAME);
			curCell.labels.add(curLabel);
		}
	}

	private void plotPolygon(Poly poly, PsCell curCell)
	{
		Layer layer = poly.getLayer();
		Technology tech = layer.getTechnology();
		if (tech == null) return;
		getLayerMap(tech);
		int j = 0;
		for( ; j<numLayers; j++)
			if (allLayers[j].layer == poly.getLayer()) break;
		if (j >= numLayers)
			return;
		Rectangle2D polyBox = poly.getBox();
		Poly.Type style = poly.getStyle();
		Point2D [] points = poly.getPoints();
		if (style == Poly.Type.FILLED)
		{
			if (polyBox != null)
			{
				PsBox curBox = new PsBox();
				curBox.layer = j;
				curBox.visible = true;
				curBox.pos[0] = polyBox.getWidth();
				curBox.pos[1] = polyBox.getHeight();
				curBox.pos[2] = polyBox.getCenterX();
				curBox.pos[3] = polyBox.getCenterY();
				curBox.pos[2] -= curBox.pos[0]/2; // adjust center x to left edge;
				curBox.pos[3] -= curBox.pos[1]/2; // adjust center y to bottom edge
				curCell.boxes.add(curBox);
			} else
			{
				PsPoly curPoly = new PsPoly();
				curPoly.layer = j;
				int numCoords = points.length * 2;
				curPoly.coords = new double[numCoords];
				for(int k=0; k<numCoords; k++)
				{
					if ((k%2) == 0) curPoly.coords[k] = points[k/2].getX(); else
						curPoly.coords[k] = points[k/2].getY();
				}
				curCell.polys.add(curPoly);
			}
		} else if (style == Poly.Type.CLOSED || style == Poly.Type.OPENEDT1 ||
			style == Poly.Type.OPENEDT2 || style == Poly.Type.OPENEDT3)
		{
			int type = 0;
			if (poly.getStyle() == Poly.Type.OPENEDT1) type = 1; else
				if (poly.getStyle() == Poly.Type.OPENEDT2) type = 2; else
					if (poly.getStyle() == Poly.Type.OPENEDT3) type = 3;
			for (int k = 1; k < points.length; k++)
				plotLine(j, points[k-1].getX(), points[k-1].getY(), points[k].getX(), points[k].getY(), type, curCell);
			if (poly.getStyle() == Poly.Type.CLOSED)
			{
				int k = points.length - 1;
				plotLine(j, points[k].getX(), points[k].getY(), points[0].getX(), points[0].getY(), type, curCell);
			}
		} else if (style == Poly.Type.VECTORS)
		{
			for(int k=0; k<points.length; k += 2)
				plotLine(j, points[k].getX(), points[k].getY(), points[k+1].getX(), points[k+1].getY(), 0, curCell);
		} else if (style == Poly.Type.CROSS || style == Poly.Type.BIGCROSS)
		{
			Rectangle2D bounds = poly.getBounds2D();
			double x = bounds.getCenterX();
			double y = bounds.getCenterY();
			plotLine(j, x-5, y, x+5, y, 0, curCell);
			plotLine(j, x, y+5, x, y-5, 0, curCell);
		} else if (style == Poly.Type.CROSSED)
		{
			Rectangle2D bounds = poly.getBounds2D();
			double lX = bounds.getMinX();
			double hX = bounds.getMaxX();
			double lY = bounds.getMinY();
			double hY = bounds.getMaxY();
			plotLine(j, lX, lY, lX, hY, 0, curCell);
			plotLine(j, lX, hY, hX, hY, 0, curCell);
			plotLine(j, hX, hY, hX, lY, 0, curCell);
			plotLine(j, hX, lY, lX, lY, 0, curCell);
			plotLine(j, hX, hY, lX, lY, 0, curCell);
			plotLine(j, hX, lY, lX, hY, 0, curCell);
		} else if (style == Poly.Type.DISC || style == Poly.Type.CIRCLE ||
			style == Poly.Type.THICKCIRCLE || style == Poly.Type.CIRCLEARC || style == Poly.Type.THICKCIRCLEARC)
		{
			if (!curveWarning)
				System.out.println("Warning: the 'merged color' PostScript option ignores curves.  Use other color options");
			curveWarning = true;
		} else if (style.isText())
		{
			PsLabel curLabel = new PsLabel();
			curLabel.label = poly.getString();
			Rectangle2D bounds = poly.getBounds2D();
			curLabel.pos[0] = bounds.getMinX();
			curLabel.pos[1] = bounds.getMaxX();
			curLabel.pos[2] = bounds.getMinY();
			curLabel.pos[3] = bounds.getMaxY();
			curLabel.style = poly.getStyle();
			curLabel.descript = poly.getTextDescriptor();
			curCell.labels.add(curLabel);
		}
	}

	/**
	 * Method to add a line from (fx,fy) to (tx,ty) on layer "layer" to the cell "curCell".
	 */
	private void plotLine(int layer, double fx, double fy, double tx, double ty, int texture, PsCell curCell)
	{
		PsPoly curPoly = new PsPoly();
		curPoly.layer = layer;
		curPoly.coords = new double[4];
		curPoly.coords[0] = fx;
		curPoly.coords[1] = fy;
		curPoly.coords[2] = tx;
		curPoly.coords[3] = ty;
		curCell.polys.add(curPoly);
	}

	private void genOverlapShapesAfterFlattening()
	{
		// traverse the list of boxes and create new layers where overlaps occur
		System.out.println("Generating overlap after flattening " + numLayers + " layers...");
		for (int i=0; i<numLayers; i++)
		{
			if (allLayers[i].mix1 != -1 && allLayers[i].mix2 != -1)
			{
				PxBoxQuadTree q1 = makeBoxQuadTree(allLayers[i].mix1);
				PxBoxQuadTree q2 = makeBoxQuadTree(allLayers[i].mix2);
				coaf1(q1, q2, i);
			}
		}
	}

	private PxBoxQuadTree makeBoxQuadTree(int layer)
	{
		// return if the quadtree is already generated
		if (quadTrees[layer] != null) return quadTrees[layer];

		// otherwise find number of boxes on this layer
		int numBoxes = 0;
		for(PsBox g : flattenedBoxes.get(layer))
		{
			if (g.visible) numBoxes++;
		}

		// and convert the list into a quad tree for faster processing

		// first allocate the quad tree
		quadTrees[layer] = new PxBoxQuadTree();
		quadTrees[layer].bounds[0] = psBoundaries[0];
		quadTrees[layer].bounds[1] = psBoundaries[1];
		quadTrees[layer].bounds[2] = psBoundaries[2];
		quadTrees[layer].bounds[3] = psBoundaries[3];
		quadTrees[layer].tl = null;
		quadTrees[layer].tr = null;
		quadTrees[layer].bl = null;
		quadTrees[layer].br = null;
		quadTrees[layer].numBoxes = numBoxes;
		quadTrees[layer].boxes = new PsBoxElement[numBoxes];
		quadTrees[layer].parent = null;
		quadTrees[layer].level = 0;

		// then copy the boxes into the tree
		int i = 0;
		for(PsBox g : flattenedBoxes.get(layer))
		{
			if (g.visible)
			{
				quadTrees[layer].boxes[i] = new PsBoxElement();
				quadTrees[layer].boxes[i].pos[0] = g.pos[0];
				quadTrees[layer].boxes[i].pos[1] = g.pos[1];
				quadTrees[layer].boxes[i].pos[2] = g.pos[2];
				quadTrees[layer].boxes[i].pos[3] = g.pos[3];
				quadTrees[layer].boxes[i].layer = g.layer;
				quadTrees[layer].boxes[i].visible = true;
				i++;
			}
		}

		// if there are too many boxes in this layer of the tree, create subtrees
		if (numBoxes > TREETHRESHOLD)
			recursivelyMakeBoxQuadTree(quadTrees[layer]);

		return quadTrees[layer];
	}

	private void recursivelyMakeBoxQuadTree(PxBoxQuadTree q)
	{
		q.tl = new PxBoxQuadTree();
		q.tl.parent = q; q.tl.level = q.level*10+1;
		q.tr = new PxBoxQuadTree();
		q.tr.parent = q; q.tr.level = q.level*10+2;
		q.bl = new PxBoxQuadTree();
		q.bl.parent = q; q.bl.level = q.level*10+3;
		q.br = new PxBoxQuadTree();
		q.br.parent = q; q.br.level = q.level*10+4;

		// split boxes into subtrees where they fit
		splitTree(q.numBoxes, q.boxes, q.bl, q.bounds[0], q.bounds[1],
			(q.bounds[0]+q.bounds[2])/2, (q.bounds[1]+q.bounds[3])/2);
		splitTree(q.numBoxes, q.boxes, q.br, (q.bounds[0]+q.bounds[2])/2, q.bounds[1],
			q.bounds[2], (q.bounds[1]+q.bounds[3])/2);
		splitTree(q.numBoxes, q.boxes, q.tl, q.bounds[0], (q.bounds[1]+q.bounds[3])/2,
			(q.bounds[0]+q.bounds[2])/2, q.bounds[3]);
		splitTree(q.numBoxes, q.boxes, q.tr, (q.bounds[0]+q.bounds[2])/2,
			(q.bounds[1]+q.bounds[3])/2, q.bounds[2], q.bounds[3]);

		// and leave boxes that span the subtrees at the top level
		int numBoxes = 0;
		for (int i=0; i<q.numBoxes; i++)
		{
			if (q.boxes[i].visible)
			{
				// q.boxes[numBoxes] = q.boxes[i];
				q.boxes[numBoxes].layer = q.boxes[i].layer;
				q.boxes[numBoxes].visible = true;
				q.boxes[numBoxes].pos[0] = q.boxes[i].pos[0];
				q.boxes[numBoxes].pos[1] = q.boxes[i].pos[1];
				q.boxes[numBoxes].pos[2] = q.boxes[i].pos[2];
				q.boxes[numBoxes].pos[3] = q.boxes[i].pos[3];
				numBoxes++;
			}
		}

		q.numBoxes = numBoxes;
	}

	private void splitTree(int numBoxes, PsBoxElement [] boxes, PxBoxQuadTree q,
		double left, double bot, double right, double top)
	{
		// count how many boxes are in subtree
		int count = 0;
		for (int i = 0; i<numBoxes; i++)
		{
			if (boxes[i].visible && boxes[i].pos[2] >= left && boxes[i].pos[3] >= bot &&
				(boxes[i].pos[2]+boxes[i].pos[0]) <= right && (boxes[i].pos[3]+boxes[i].pos[1]) <= top) count++;
		}

		// and copy them into a new array for the subtree
		q.boxes = new PsBoxElement[count];
		count = 0;
		for (int i=0; i<numBoxes; i++)
		{
			if (boxes[i].visible && boxes[i].pos[2] >= left && boxes[i].pos[3] >= bot &&
				(boxes[i].pos[2]+boxes[i].pos[0]) <= right && (boxes[i].pos[3]+boxes[i].pos[1]) <= top)
			{
				q.boxes[count] = new PsBoxElement();
				q.boxes[count].layer = boxes[i].layer;
				q.boxes[count].visible = true;
				q.boxes[count].pos[0] = boxes[i].pos[0];
				q.boxes[count].pos[1] = boxes[i].pos[1];
				q.boxes[count].pos[2] = boxes[i].pos[2];
				q.boxes[count].pos[3] = boxes[i].pos[3];
				boxes[i].visible = false; // mark box for removal from upper level
				count++;
			}
		}

		q.numBoxes = count;
		q.bounds[0] = left;
		q.bounds[1] = bot;
		q.bounds[2] = right;
		q.bounds[3] = top;
		q.tl = null;
		q.tr = null;
		q.bl = null;
		q.br = null;

		if (count > TREETHRESHOLD)
		{
			recursivelyMakeBoxQuadTree(q);
		}
	}

	private void mergeBoxes()
	{
//		int numMerged = 0;

		System.out.println("Merging boxes for " + totalCells + " cells...");
		for(PsCell c : allCells)
		{
			boolean changed = false;
			do {
				changed = false;
				for(int i1 = 0; i1 < c.boxes.size(); i1++)
				{
					PsBox b1 = c.boxes.get(i1);
					for(int i2 = i1+1; i2 < c.boxes.size(); i2++)
					{
						PsBox b2 = c.boxes.get(i2);
						if (b1.layer == b2.layer && b1.visible && b2.visible)
							if (mergeBoxPair(b1, b2)) changed = true;
					}
				}
			} while (changed);
//			numMerged++;
		}
	}

	private boolean mergeBoxPair(PsBox bx1, PsBox bx2)
	{
		double t0 = bx1.pos[3] + bx1.pos[1];
		double b0 = bx1.pos[3];
		double l0 = bx1.pos[2];
		double r0 = bx1.pos[2] + bx1.pos[0];
		double t1 = bx2.pos[3] + bx2.pos[1];
		double b1 = bx2.pos[3];
		double l1 = bx2.pos[2];
		double r1 = bx2.pos[2] + bx2.pos[0];

		// if the boxes coincide, expand the first one and hide the second one
		if (t0 == t1 && b0 == b1 && (Math.min(r0, r1) > Math.max(l0, l1)))
		{
			double l2 = Math.min(l0, l1);
			double r2 = Math.max(r0, r1);
			bx1.pos[0] = r2-l2;
			bx1.pos[1] = t0-b0;
			bx1.pos[2] = l2;
			bx1.pos[3] = b0;
			bx2.visible = false;
			return true;
		} else if (r0 == r1 && l0 == l1 && (Math.min(t0, t1) > Math.max(b0, b1)))
		{
			double b2 = Math.min(b0, b1);
			double t2 = Math.max(t0, t1);
			bx1.pos[0] = r0-l0;
			bx1.pos[1] = t2-b2;
			bx1.pos[2] = l0;
			bx1.pos[3] = b2;
			bx2.visible = false;
			return true;
		}
		// if one completely covers another, hide the covered box
		else if (r0 >= r1 && l0 <= l1 && t0 >= t1 && b0 <= b1)
		{
			bx2.visible = false;
			return true;
		} else if (r1 >= r0 && l1 <= l0 && t1 >= t0 && b1 <= b0)
		{
			bx1.visible = false;
			return true;
		}
		return false;
	}

	private void coaf1(PxBoxQuadTree q1, PxBoxQuadTree q2, int layerNum)
	{
		coaf2(q1, q2, layerNum);
		if (q1.tl != null)
		{
			coaf3(q1.tl, q2, layerNum);
			coaf3(q1.tr, q2, layerNum);
			coaf3(q1.bl, q2, layerNum);
			coaf3(q1.br, q2, layerNum);
			if (q2.tl != null)
			{
				coaf1(q1.tl, q2.tl, layerNum);
				coaf1(q1.tr, q2.tr, layerNum);
				coaf1(q1.bl, q2.bl, layerNum);
				coaf1(q1.br, q2.br, layerNum);
			} else
			{
				coaf4(q1.tl, q2, layerNum, false);
				coaf4(q1.tr, q2, layerNum, false);
				coaf4(q1.bl, q2, layerNum, false);
				coaf4(q1.br, q2, layerNum, false);
			}
		}
	}

	private void coaf2(PxBoxQuadTree q1, PxBoxQuadTree q2, int layerNum)
	{
		checkOverlapAfterFlattening(q1, q2, layerNum);
		if (q2.tl != null)
		{
			coaf2(q1, q2.tl, layerNum);
			coaf2(q1, q2.tr, layerNum);
			coaf2(q1, q2.bl, layerNum);
			coaf2(q1, q2.br, layerNum);
		}
	}

	private void coaf3(PxBoxQuadTree q1, PxBoxQuadTree q2, int layerNum)
	{
		checkOverlapAfterFlattening(q1, q2, layerNum);
		if (q2.parent != null)
			coaf3(q1, q2.parent, layerNum);
	}

	private void coaf4(PxBoxQuadTree q1, PxBoxQuadTree q2, int layerNum, boolean check)
	{
		if (check)
		{
			coaf3(q1, q2, layerNum);
		}
		if (q1.tl != null)
		{
			coaf4(q1.tl, q2, layerNum, true);
			coaf4(q1.tr, q2, layerNum, true);
			coaf4(q1.bl, q2, layerNum, true);
			coaf4(q1.br, q2, layerNum, true);
		}
	}

	private void checkOverlapAfterFlattening(PxBoxQuadTree q1, PxBoxQuadTree q2, int layerNum)
	{
		// check overlap of boxes at this level of the quad tree
		double [] t = new double[3];
		double [] b = new double[3];
		double [] l = new double[3];
		double [] r = new double[3];
		if (q1.numBoxes != 0 && q2.numBoxes != 0)
		{
			for (int j=0; j<q1.numBoxes; j++)
			{
				t[0] = q1.boxes[j].pos[3] + q1.boxes[j].pos[1];
				b[0] = q1.boxes[j].pos[3];
				l[0] = q1.boxes[j].pos[2];
				r[0] = q1.boxes[j].pos[2] + q1.boxes[j].pos[0];

				for (int k=0; k < q2.numBoxes; k++)
				{
					t[1] = q2.boxes[k].pos[3] + q2.boxes[k].pos[1];
					b[1] = q2.boxes[k].pos[3];
					l[1] = q2.boxes[k].pos[2];
					r[1] = q2.boxes[k].pos[2] + q2.boxes[k].pos[0];

					t[2] = t[0] < t[1] ? t[0] : t[1];
					b[2] = b[0] > b[1] ? b[0] : b[1];
					l[2] = l[0] > l[1] ? l[0] : l[1];
					r[2] = r[0] < r[1] ? r[0] : r[1];

					if (t[2] > b[2] && r[2] > l[2])
					{
						// create overlap layer
						PsBox curBox = new PsBox();
						curBox.layer = layerNum;
						curBox.pos[0] = (r[2]-l[2]);
						curBox.pos[1] = (t[2]-b[2]);
						curBox.pos[2] = (l[2]);
						curBox.pos[3] = (b[2]);
						curBox.visible = true;
						if (t[2] == t[0] && b[2] == b[0] && l[2] == l[0] && r[2] == r[0]) q1.boxes[j].visible = false;
						if (t[2] == t[1] && b[2] == b[1] && l[2] == l[1] && r[2] == r[1]) q2.boxes[k].visible = false;
						flattenedBoxes.get(layerNum).add(curBox);
					}
				}
			}
		}
	}

	private void flatten()
	{
		double [] ident = new double[9];
		newIdentityMatrix(ident);
		for (int i=0; i<numLayers; i++)
		{
			flattenedPolys.add(new ArrayList<PsPoly>());
			flattenedBoxes.add(new ArrayList<PsBox>());
		}

		// for now, assume last cell is top level.  Change this to recognize C *** at top of CIF
		System.out.println("Flattening...");
		int lastCell = allCells.size() - 1;
		PsCell topCell = allCells.get(lastCell);
		recursiveFlatten(topCell, ident);
	}

	private void recursiveFlatten(PsCell topCell, double [] m)
	{
		// add boxes from this cell
		for(PsBox box : topCell.boxes)
		{
			if (box.visible)
			{
				PsBox newBox = copyBox(box, m);
				flattenedBoxes.get(newBox.layer).add(newBox);
			}
		}

		// add polygons from this cell
		for(PsPoly poly : topCell.polys)
		{
			PsPoly newPoly = copyPoly(poly, m);
			flattenedPolys.get(newPoly.layer).add(newPoly);
		}

		// recursively traverse sub-instances
		double [] tm = new double[9];
		for(PsCellInst inst : topCell.inst)
		{
//			totalInstances++;
			matrixMul(tm, inst.transform, m);
			recursiveFlatten(inst.inst, tm);
		}
	}

	private void writePS(Cell cell, boolean usePlotter, double pageWidth, double pageHeight, double border, EditWindow0 wnd)
	{
		// Header info
		PrintWriter printWriter = psObject.printWriter;
		printWriter.println("%%!PS-Adobe-1.0");
		printWriter.println("%%Title: " + cell.describe(false));
		if (User.isIncludeDateAndVersionInOutput())
		{
			printWriter.println("%%%%Creator: Electric VLSI Design System (David Harris's color PostScript generator) version " +
				Version.getVersion());
			printWriter.println("%%%%CreationDate: " + TextUtils.formatDate(new Date()));
		} else
		{
			printWriter.println("%%%%Creator: Electric VLSI Design System (David Harris's color PostScript generator)");
		}
		printWriter.println("%%%%Pages: 1");
		printWriter.println("%%%%BoundingBox: " + TextUtils.formatDouble(psBoundaries[0]) + " " +
			TextUtils.formatDouble(psBoundaries[1]) + " " +
			TextUtils.formatDouble(psBoundaries[2]) + " " +
			TextUtils.formatDouble(psBoundaries[3]));
		printWriter.println("%%%%DocumentFonts: " + FONTNAME);
		printWriter.println("%%%%EndComments");

		// Coordinate system
		printWriter.println("%% Min X: " + TextUtils.formatDouble(psBoundaries[0]) +
			"  min Y: " + TextUtils.formatDouble(psBoundaries[1]) +
			"  max X: " + TextUtils.formatDouble(psBoundaries[2]) +
			"  max Y: " + TextUtils.formatDouble(psBoundaries[3]));
		double reducedwidth = pageWidth - border*2;
		double reducedheight = pageHeight - border*2;
		double scale = reducedwidth/(psBoundaries[2]-psBoundaries[0]);
		if (usePlotter)
		{
			// plotter: infinite height
			printWriter.println(TextUtils.formatDouble(scale) + " " + TextUtils.formatDouble(scale) + " scale");
			double x = psBoundaries[0]+(psBoundaries[2]-psBoundaries[0]) / 2 -
				(reducedwidth/scale) / 2 - border/scale;
			double y = psBoundaries[1] - border/scale;
			printWriter.println(TextUtils.formatDouble(x) + " neg " + TextUtils.formatDouble(y) + " neg translate");
		} else
		{
			// printer: fixed height
			if (reducedheight/(psBoundaries[3]-psBoundaries[1]) < scale)
				scale = reducedheight/(psBoundaries[3]-psBoundaries[1]);
			printWriter.println(TextUtils.formatDouble(scale) + " " + TextUtils.formatDouble(scale) + " scale");
			double x = psBoundaries[0]+(psBoundaries[2]-psBoundaries[0]) / 2 -
				(reducedwidth/scale) / 2 - border/scale;
			double y = psBoundaries[1]+(psBoundaries[3]-psBoundaries[1]) / 2 -
				(reducedheight/scale) / 2 - border/scale;
			printWriter.println(TextUtils.formatDouble(x) + " neg " + TextUtils.formatDouble(y) + " neg translate");
		}

		// set font
		printWriter.println("/DefaultFont /" + FONTNAME + " def");
		printWriter.println("/scaleFont {");
		printWriter.println("    DefaultFont findfont");
		printWriter.println("    exch scalefont setfont} def");

		// define box command to make rectangles more memory efficient
		printWriter.println("\n/bx \n  { /h exch def /w exch def /x exch def /y exch def");
		printWriter.println("    newpath x y moveto w 0 rlineto 0 h rlineto w neg 0 rlineto closepath fill } def");

		// draw layers
		for (int i=0; i<numLayers; i++)
		{
			// skip drawing layers that are white
			if (allLayers[i].r != 1 || allLayers[i].g != 1 || allLayers[i].b != 1)
			{
				List<PsBox> g = flattenedBoxes.get(i);
				List<PsPoly> p = flattenedPolys.get(i);
				if (g.size() > 0 || p.size() > 0)
				{
					StringBuffer buf = new StringBuffer();
					makeLayerName(i, buf);
					printWriter.println();
					printWriter.println("%% Layer" + buf.toString());
					printWriter.println(TextUtils.formatDouble(allLayers[i].r) + " " +
						TextUtils.formatDouble(allLayers[i].g) + " " +
						TextUtils.formatDouble(allLayers[i].b) + " setrgbcolor");
				}
				for(PsBox gB : g)
				{
					if (gB.visible)
					{
						double w = gB.pos[0];
						double h = gB.pos[1];
						printWriter.println(gB.pos[3] + " " + gB.pos[2] + " " + w + " " + h + " bx");
//						totalBoxes++;
					}
				}
				for(PsPoly pB : p)
				{
					if (pB.coords.length > 2)
					{
						printWriter.println("newpath " + TextUtils.formatDouble(pB.coords[0]) + " " +
							TextUtils.formatDouble(pB.coords[1]) + " moveto");
						for (int j=2; j<pB.coords.length; j+=2)
						{
							printWriter.println("        " + TextUtils.formatDouble(pB.coords[j]) + " " +
									TextUtils.formatDouble(pB.coords[j+1]) + " lineto");
						}
						printWriter.println("closepath " + (i==0 ? "stroke" : "fill"));
//						totalPolys++;
					}
				}
			}
		}

		// label ports and cell instances
		PsCell topCell = allCells.get(0);
		printWriter.println();
		printWriter.println("%% Port and Cell Instance Labels");
		printWriter.println("0 0 0 setrgbcolor");
		for(int i=0; i<psObject.headerString.length; i++)
			printWriter.println(psObject.headerString[i]);
		for(PsLabel l : topCell.labels)
		{
			double size = 14;
			TextDescriptor.Size s = l.descript.getSize();
			if (s != null)
			{
				// absolute font sizes are easy
				if (s.isAbsolute()) size = s.getSize(); else
				{
					// relative font: get size in grid units
					size = s.getSize();
				}
			}
			if (wnd != null) size *= wnd.getGlobalTextScale();
			double psLX = l.pos[0];   double psHX = l.pos[1];
			double psLY = l.pos[2];   double psHY = l.pos[3];
			if (l.style == Poly.Type.TEXTBOX)
			{
				printWriter.print(TextUtils.formatDouble((psLX+psHX)/2) + " " +
					TextUtils.formatDouble((psLY+psHY)/2) + " " +
					TextUtils.formatDouble(psHX-psLX) + " " +
					TextUtils.formatDouble(psHY-psLY) + " ");
				psObject.writePSString(l.label);
				printWriter.println(" " + TextUtils.formatDouble(size/scale) + " Boxstring");
			} else
			{
				double x = 0, y = 0;
				String opName = "";
				if (l.style == Poly.Type.TEXTCENT)
				{
					x = (psLX+psHX)/2;   y = (psLY+psHY)/2;
					opName = "Centerstring";
				} else if (l.style == Poly.Type.TEXTTOP)
				{
					x = (psLX+psHX)/2;   y = psHY;
					opName = "Topstring";
				} else if (l.style == Poly.Type.TEXTBOT)
				{
					x = (psLX+psHX)/2;   y = psLY;
					opName = "Botstring";
				} else if (l.style == Poly.Type.TEXTLEFT)
				{
					x = psLX;   y = (psLY+psHY)/2;
					opName = "Leftstring";
				} else if (l.style == Poly.Type.TEXTRIGHT)
				{
					x = psHX;   y = (psLY+psHY)/2;
					opName = "Rightstring";
				} else if (l.style == Poly.Type.TEXTTOPLEFT)
				{
					x = psLX;   y = psHY;
					opName = "Topleftstring";
				} else if (l.style == Poly.Type.TEXTTOPRIGHT)
				{
					x = psHX;   y = psHY;
					opName = "Toprightstring";
				} else if (l.style == Poly.Type.TEXTBOTLEFT)
				{
					x = psLX;   y = psLY;
					opName = "Botleftstring";
				} else if (l.style == Poly.Type.TEXTBOTRIGHT)
				{
					x = psHX;   y = psLY;
					opName = "Botrightstring";
				}
				double descenderOffset = size / 12;
				TextDescriptor.Rotation rot = l.descript.getRotation();
				if (rot == TextDescriptor.Rotation.ROT0) y += descenderOffset; else
					if (rot == TextDescriptor.Rotation.ROT90) x -= descenderOffset; else
						if (rot == TextDescriptor.Rotation.ROT180) y -= descenderOffset; else
							if (rot == TextDescriptor.Rotation.ROT270) x += descenderOffset;
				double xOff = x;   double yOff = y;
				if (rot != TextDescriptor.Rotation.ROT0)
				{
					if (rot == TextDescriptor.Rotation.ROT90 || rot == TextDescriptor.Rotation.ROT270)
					{
						if (l.style == Poly.Type.TEXTTOP) opName = "Rightstring"; else
							if (l.style == Poly.Type.TEXTBOT) opName = "Leftstring"; else
								if (l.style == Poly.Type.TEXTLEFT) opName = "Botstring"; else
									if (l.style == Poly.Type.TEXTRIGHT) opName = "Topstring"; else
										if (l.style == Poly.Type.TEXTTOPLEFT) opName = "Botrightstring"; else
											if (l.style == Poly.Type.TEXTBOTRIGHT) opName = "Topleftstring";
					}
					x = y = 0;
					if (rot == TextDescriptor.Rotation.ROT90)
					{
						printWriter.println(xOff + " " + yOff + " translate 90 rotate");
					} else if (rot == TextDescriptor.Rotation.ROT180)
					{
						printWriter.println(xOff + " " + yOff + " translate 180 rotate");
					} else if (rot == TextDescriptor.Rotation.ROT270)
					{
						printWriter.println(xOff + " " + yOff + " translate 270 rotate");
					}
				}
				printWriter.print(x + " " + y + " ");
				psObject.writePSString(l.label);
				printWriter.println(" " + size + " " + opName);
				if (rot != TextDescriptor.Rotation.ROT0)
				{
					if (rot == TextDescriptor.Rotation.ROT90)
					{
						printWriter.println("270 rotate " + (-xOff) + " " + (-yOff) + " translate");
					} else if (rot == TextDescriptor.Rotation.ROT180)
					{
						printWriter.println("180 rotate " + (-xOff) + " " + (-yOff) + " translate");
					} else if (rot == TextDescriptor.Rotation.ROT270)
					{
						printWriter.println("90 rotate " + (-xOff) + " " + (-yOff) + " translate");
					}
				}
			}
		}

		// Finish page
		printWriter.println("\nshowpage");
	}

	private void makeLayerName(int index, StringBuffer buf)
	{
		if (allLayers[index].layer != null)
		{
			buf.append(" ");
			buf.append(allLayers[index].layer.getName());
			return;
		}
		makeLayerName(allLayers[index].mix1, buf);
		makeLayerName(allLayers[index].mix2, buf);
	}

	private PsBox copyBox(PsBox g, double [] m)
	{
		PsBox newBox = new PsBox();
		newBox.layer = g.layer;
		transformBox(newBox.pos, g.pos, m);
		newBox.visible = g.visible;

		// Update bounding box
		double dx = newBox.pos[0];
		double dy = newBox.pos[1];
		if (newBox.pos[2] < psBoundaries[0]) psBoundaries[0] = newBox.pos[2];
		if (newBox.pos[3] < psBoundaries[1]) psBoundaries[1] = newBox.pos[3];
		if (newBox.pos[2]+dx > psBoundaries[2]) psBoundaries[2] = newBox.pos[2]+dx;
		if (newBox.pos[3]+dy > psBoundaries[3]) psBoundaries[3] = newBox.pos[3]+dy;
		return newBox;
	}

	private PsPoly copyPoly(PsPoly p, double [] m)
	{
		PsPoly newPoly = new PsPoly();
		newPoly.layer = p.layer;
		int numCoords = p.coords.length;
		newPoly.coords = new double[numCoords];
		transformPoly(newPoly, p, m);

		// Update bounding box
		for(int i=0; i<numCoords; i++)
		{
			if ((i%2) == 0)
			{
				if (newPoly.coords[i] < psBoundaries[0]) psBoundaries[0] = newPoly.coords[i];
				if (newPoly.coords[i] > psBoundaries[3]) psBoundaries[3] = newPoly.coords[i];
			} else
			{
				if (newPoly.coords[i] < psBoundaries[1]) psBoundaries[1] = newPoly.coords[i];
				if (newPoly.coords[i] > psBoundaries[2]) psBoundaries[2] = newPoly.coords[i];
			}
		}
		return newPoly;
	}

	private void matrixMul(double [] r, double [] a, double [] b)
	{
		double [] tmp = new double[9];

		for (int i=0; i<3; i++)
		{
			for (int j=0; j<3; j++)
			{
				tmp[i*3+j] = 0;
				for (int k=0; k<3; k++)
				{
					tmp[i*3+j] += a[i*3+k] * b[k*3+j];
				}
			}
		}
		for (int i=0; i<3; i++)
		{
			for (int j=0; j<3; j++)
			{
				r[i*3+j] = tmp[i*3+j];
			}
		}
	}

	private void newIdentityMatrix(double [] m)
	{
		for (int i=0; i<3; i++)
		{
			for (int j=0; j<3; j++)
			{
				m[i*3+j] = (i==j ? 1 : 0);
			}
		}
	}

	private void transformBox(double [] finall, double [] initial, double [] m)
	{
		double [] pos = new double[2];
		pos[0] = initial[2]+initial[0]/2;
		pos[1] = initial[3]+initial[1]/2;

		for (int i=0; i < 2; i++)
		{
			finall[i+2] = m[6+i];
			for (int j=0; j<2; j++)
			{
				finall[i+2] += m[i+j*3]*pos[j];
			}
		}

		if (m[1] == 0)
		{
			// rotation
			finall[0] = initial[0];
			finall[1] = initial[1];
		} else
		{
			finall[0] = initial[1];
			finall[1] = initial[0];
		}
		finall[2] -= finall[0]/2;
		finall[3] -= finall[1]/2;
	}

	private void transformPoly(PsPoly finall, PsPoly initial, double [] m)
	{
		for (int p=0; p<initial.coords.length/2; p++)
		{
			for (int i=0; i <2; i++)
			{
				finall.coords[p*2+i] = m[6+i];
				for (int j=0; j<2; j++)
				{
					finall.coords[p*2+i] += m[i+j*3]*initial.coords[p*2+j];
				}
			}
		}
	}

//	private void printStatistics()
//	{
//		System.out.println("Plotting statistics:");
//		System.out.println("  " + numLayers + " layers defined or transparencies implied in layer map");
//		System.out.println("  " + totalCells + " cells");
//		System.out.println("  " + totalInstances + " instances used");
//		System.out.println("  " + totalBoxes + " boxes generated");
//		System.out.println("  " + totalPolys + " polygons generated");
//	}
}
