/* -*- 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 Sun Microsystems and Static Free Software
*
* 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 Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.io.output;
import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.geometry.Orientation;
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.TextUtils;
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.EditWindow_;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.user.User;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
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
* eps
*/
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
{
int cellNum;
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 int cellNumber;
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 cell the Cell being written.
* @param epsFormat true to write encapsulated PostScript.
* @param usePlotter true for an infinitely-tall plotter, where page height is not a consideration.
* @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, Cell cell, boolean epsFormat, boolean usePlotter,
double pageWid, double pageHei, double pageMargin)
{
PostScriptColor psc = new PostScriptColor(psObject);
psc.doPrinting(cell, epsFormat, usePlotter, pageWid, pageHei, pageMargin);
}
private void doPrinting(Cell cell, boolean epsFormat, boolean usePlotter,
double pageWid, double pageHei, double pageMargin)
{
totalBoxes = totalCells = totalPolys = 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;
cellNumber = 1;
// 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);
mergeBoxes();
flatten();
genOverlapShapesAfterFlattening();
writePS(cell, usePlotter, pageWid, pageHei, pageMargin);
// 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>();
for(int i=0; i<tech.getNumLayers(); i++)
{
Layer layer = tech.getLayer(i);
if (layer.isPseudoLayer()) continue;
layerSorts.add(layer);
}
// sort by layer height
Collections.sort(layerSorts, new LayersByDepth());
// 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++;
}
}
/**
* Comparator class for sorting Layers by their height.
*/
private static class LayersByDepth implements Comparator<Layer>
{
/**
* Method to sort LayerSort by their height.
*/
public int compare(Layer l1, Layer l2)
{
double diff = l1.getDepth() - l2.getDepth();
if (diff == 0.0) return 0;
if (diff < 0.0) return -1;
return 1;
}
}
private void extractDatabase(Cell cell)
{
// 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 (!ni.isExpanded()) continue;
Cell subCell = (Cell)ni.getProto();
if (cellStructs.get(subCell) != null) continue;
extractDatabase(subCell);
}
// create a cell
PsCell curCell = new PsCell();
curCell.cellNum = cellNumber++;
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 (!ni.isExpanded())
{
// 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
AffineTransform 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 subinstances
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)
{
// 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();
}
}
EditWindow_ wnd = Job.getUserInterface().getCurrentEditWindow_();
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");
// }
}