/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PostScript.java
* Input/output tool: PostScript output
* Written by Steven M. Rubin, Sun Microsystems.
*
* Copyright (c) 2004, Oracle and/or its affiliates. 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 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.ERectangle;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.EditWindow0;
import com.sun.electric.database.variable.EditWindow_;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.TechPool;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.io.IOTool;
import com.sun.electric.tool.user.GraphicsPreferences;
import com.sun.electric.tool.user.User.ColorPrefType;
import com.sun.electric.tool.user.ui.LayerVisibility;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
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.
*/
public class PostScript extends Output
{
/** scale factor for PostScript */ private static final int PSSCALE = 4;
// /** scale factor for PostScript text */ private static final double PSTEXTSCALE = 0.45;
/** scale factor for PostScript text */ private static final double PSTEXTSCALE = 0.75;
/** size of text in the corner */ private static final int CORNERDATESIZE = 14;
/** default text plain font */ private static final String DEFAULTFONT = "Times-Roman";
/** default text bold font */ private static final String DEFAULTFONTBOLD = "Times-Bold";
/** default text italic font */ private static final String DEFAULTFONTITALIC = "Times-Italic";
/** default text bold-italic font */ private static final String DEFAULTFONTBI = "Times-BoldItalic";
/** write macros for dot drawing */ private static final int HEADERDOT = 1;
/** write macros for line drawing */ private static final int HEADERLINE = 2;
/** write macros for polygon drawing */ private static final int HEADERPOLYGON = 3;
/** write macros for filled polygon drawing */ private static final int HEADERFPOLYGON = 4;
/** write macros for text drawing */ private static final int HEADERSTRING = 5;
/** true if the "dot" header code has been written. */ private boolean putHeaderDot;
/** true if the "line" header code has been written. */ private boolean putHeaderLine;
/** true if the "polygon" header code has been written. */ private boolean putHeaderPolygon;
/** true if the "filled polygon" header code has been written. */ private boolean putHeaderFilledPolygon;
/** true if the "string" header code has been written. */ private boolean putHeaderString;
/** true to generate color PostScript. */ private boolean psUseColor;
/** true to generate merged color PostScript. */ private boolean psUseColorMerge;
/** the Cell being written. */ private Cell cell;
/** number of patterns emitted so far. */ private int psNumPatternsEmitted;
/** list of patterns emitted so far. */ private Map<EGraphics,Integer> patternsEmitted;
/** current layer number (-1: do all; 0: cleanup). */ private int currentLayer;
/** the last color written out. */ private int lastColor;
/** the normal width of lines. */ private int lineWidth;
/** matrix from database units to PS units. */ private AffineTransform matrix;
/** fake graphics for drawing outlines and text. */ private static EGraphics blackGraphics =
new EGraphics(false, false, null, 0, 100,100,100,1.0,true, new int[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0});
PostScriptPreferences localPrefs;
public static class PostScriptPreferences extends OutputPreferences
{
/** list of Polys to use instead of cell contents. */ List<PolyBase> override;
boolean plotDates = IOTool.isFactoryPlotDate();
int printColorMethod = IOTool.getFactoryPrintColorMethod();
boolean printForPlotter = IOTool.isFactoryPrintForPlotter();
boolean printEncapsulate = IOTool.isFactoryPrintEncapsulated();
double pageWidth = IOTool.getFactoryPrintWidth();
double pageHeight = IOTool.getFactoryPrintHeight();
double printMargin = IOTool.getFactoryPrintMargin();
int printRotation = IOTool.getFactoryPrintRotation();
double printPSLineWidth = IOTool.getFactoryPrintPSLineWidth();
GraphicsPreferences gp;
EditWindow0.EditWindowSmall wnd;
ERectangle printBounds;
Set<Layer> invisibleLayers = new HashSet<Layer>();
boolean isGrid = false;
double gridXSpacing, gridYSpacing;
boolean showTempNames;
PostScriptPreferences(boolean factory, List<PolyBase> override)
{
super(factory);
this.override = override;
gp = new GraphicsPreferences(factory);
LayerVisibility lv = new LayerVisibility(factory);
for (Technology tech: TechPool.getThreadTechPool().values()) {
for (Iterator<Layer> it = tech.getLayers(); it.hasNext(); ) {
Layer layer = it.next();
if (!lv.isVisible(layer))
invisibleLayers.add(layer);
}
}
if (!factory)
fillPrefs();
}
private void fillPrefs()
{
plotDates = IOTool.isPlotDate();
printColorMethod = IOTool.getPrintColorMethod();
printForPlotter = IOTool.isPrintForPlotter();
printEncapsulate = IOTool.isPrintEncapsulated();
pageWidth = IOTool.getPrintWidth();
pageHeight = IOTool.getPrintHeight();
printMargin = IOTool.getPrintMargin();
printRotation = IOTool.getPrintRotation();
printPSLineWidth = IOTool.getPrintPSLineWidth();
UserInterface ui = Job.getUserInterface();
EditWindow_ localWnd = ui.getCurrentEditWindow_();
wnd = new EditWindow0.EditWindowSmall(localWnd);
isGrid = localWnd.isGrid();
gridXSpacing = localWnd.getGridXSpacing();
gridYSpacing = localWnd.getGridYSpacing();
// determine the area of interest
printBounds = null;
if (override != null)
{
double lX=0, hX=0, lY=0, hY=0;
boolean first = true;
for(PolyBase poly : override)
{
Point2D [] points = poly.getPoints();
for(int i=0; i<points.length; i++)
{
double x = points[i].getX();
double y = points[i].getY();
if (first)
{
first = false;
lX = hX = x;
lY = hY = y;
} else
{
if (x < lX) lX = x;
if (x > hX) hX = x;
if (y < lY) lY = y;
if (y > hY) hY = y;
}
}
}
printBounds = ERectangle.fromLambda(lX, lY, hX-lX, hY-lY);
} else
{
Cell cell = localWnd.getCell();
printBounds = ERectangle.fromLambda(getAreaToPrint(cell, false, localWnd));
}
}
public Output doOutput(Cell cell, VarContext context, String filePath)
{
PostScript out = new PostScript(this, cell);
out.writeCellToFile(filePath);
return out.finishWrite();
}
}
/**
* PostScript constructor.
*/
private PostScript(PostScriptPreferences pp, Cell cell)
{
localPrefs = pp;
this.cell = cell;
}
/**
* Internal method for PostScript output.
* @param filePath the disk file to create.
*/
private boolean writeCellToFile(String filePath)
{
if (localPrefs.printBounds == null) return true;
boolean error = false;
if (openTextOutputStream(filePath)) error = true; else
{
// write out the cell
if (cell.getView().isTextView())
{
// text cell
printWriter.println("Library: " + cell.getLibrary().getName() + " Cell: " + cell.noLibDescribe());
if (localPrefs.includeDateAndVersionInOutput)
{
printWriter.println(" Created: " + TextUtils.formatDate(cell.getCreationDate()) +
" Revised: " + TextUtils.formatDate(cell.getRevisionDate()));
}
printWriter.println("\n\n");
// print the text of the cell
Variable var = cell.getVar(Cell.CELL_TEXT_KEY);
if (var != null)
{
String [] strings = (String [])var.getObject();
for(int i=0; i<strings.length; i++)
printWriter.println(strings[i]);
}
} else
{
// layout/schematics cell
if (start())
{
scanCircuit();
done();
}
}
if (closeTextOutputStream()) error = true;
}
if (!error)
{
System.out.println(filePath + " written");
}
return error;
}
/**
* Method to initialize for writing a cell.
* @return false to abort the process.
*/
private boolean start()
{
// find the edit window
// if (localPrefs.wnd != null && localPrefs.wnd.getCell() != cell) localPrefs.wnd = null;
// clear flags that tell whether headers have been included
putHeaderDot = false;
putHeaderLine = false;
putHeaderPolygon = false;
putHeaderFilledPolygon = false;
putHeaderString = false;
// get control options
psUseColor = psUseColorMerge = false;
switch (localPrefs.printColorMethod)
{
case 1: // color
psUseColor = true;
break;
case 2: // color stippled
psUseColor = true;
break;
case 3: // color merged
psUseColor = psUseColorMerge = true;
break;
}
double pageWid = localPrefs.pageWidth * 75;
double pageHei = localPrefs.pageHeight * 75;
double pageMarginPS = localPrefs.printMargin * 75;
double pageMargin = pageMarginPS; // not right!!!
boolean rotatePlot = false;
switch (localPrefs.printRotation)
{
case 1: // rotate 90 degrees
rotatePlot = true;
break;
case 2: // auto-rotate
if (((pageHei > pageWid || localPrefs.printForPlotter) &&
localPrefs.printBounds.getWidth() > localPrefs.printBounds.getHeight()) ||
(pageWid > pageHei && localPrefs.printBounds.getHeight() > localPrefs.printBounds.getWidth()))
rotatePlot = true;
break;
}
// if plotting, compute height from width
if (localPrefs.printForPlotter)
{
if (rotatePlot)
{
pageHei = pageWid * localPrefs.printBounds.getWidth() / localPrefs.printBounds.getHeight();
} else
{
pageHei = pageWid * localPrefs.printBounds.getHeight() / localPrefs.printBounds.getWidth();
}
}
// for pure color plotting, use special merging code
if (psUseColorMerge && localPrefs.override == null)
{
PostScriptColor.psColorPlot(this, cell, localPrefs.printEncapsulate, localPrefs.printForPlotter,
pageWid, pageHei, pageMarginPS, localPrefs.wnd);
return false;
}
// PostScript: compute the transformation matrix
double cX = localPrefs.printBounds.getCenterX();
double cY = localPrefs.printBounds.getCenterY();
double unitsX = (pageWid-pageMargin*2) * PSSCALE;
double unitsY = (pageHei-pageMargin*2) * PSSCALE;
if (localPrefs.printEncapsulate)
{
double scale = IOTool.getPrintEPSScale(cell);
if (scale != 0)
{
unitsX *= scale;
unitsY *= scale;
}
}
double i, j;
if (localPrefs.printForPlotter)
{
i = unitsX / localPrefs.printBounds.getWidth();
j = unitsX / localPrefs.printBounds.getHeight();
} else
{
i = Math.min(unitsX / localPrefs.printBounds.getWidth(), unitsY / localPrefs.printBounds.getHeight());
j = Math.min(unitsX / localPrefs.printBounds.getHeight(), unitsY / localPrefs.printBounds.getWidth());
}
if (rotatePlot) i = j;
double matrix00 = i; double matrix01 = 0;
double matrix10 = 0; double matrix11 = i;
double matrix20 = - i * cX + unitsX / 2 + pageMarginPS * PSSCALE;
double matrix21;
if (localPrefs.printForPlotter)
{
matrix21 = - i * localPrefs.printBounds.getMinY() + pageMarginPS * PSSCALE;
} else
{
matrix21 = - i * cY + unitsY / 2 + pageMarginPS * PSSCALE;
}
matrix = new AffineTransform(matrix00, matrix01, matrix10, matrix11, matrix20, matrix21);
// write PostScript header
if (localPrefs.printEncapsulate) printWriter.println("%!PS-Adobe-2.0 EPSF-2.0"); else
printWriter.println("%!PS-Adobe-1.0");
printWriter.println("%%Title: " + cell.describe(false));
if (localPrefs.includeDateAndVersionInOutput)
{
printWriter.println("%%Creator: Electric VLSI Design System version " + Version.getVersion());
Date now = new Date();
printWriter.println("%%CreationDate: " + TextUtils.formatDate(now));
} else
{
printWriter.println("%%Creator: Electric VLSI Design System");
}
if (localPrefs.printEncapsulate) printWriter.println("%%Pages: 0"); else
printWriter.println("%%Pages: 1");
emitCopyright("% ", "");
// transform to PostScript units
double bblx = localPrefs.printBounds.getMinX();
double bbhx = localPrefs.printBounds.getMaxX();
double bbly = localPrefs.printBounds.getMinY();
double bbhy = localPrefs.printBounds.getMaxY();
Point2D bbCorner1 = psXform(new Point2D.Double(bblx, bbly));
Point2D bbCorner2 = psXform(new Point2D.Double(bbhx, bbhy));
bblx = bbCorner1.getX();
bbly = bbCorner1.getY();
bbhx = bbCorner2.getX();
bbhy = bbCorner2.getY();
if (rotatePlot)
{
// fiddle with the bounding box if image rotated on page
// (at this point, bounding box coordinates are absolute printer units)
double t1 = bblx;
double t2 = bbhx;
bblx = -bbhy + pageHei * 300 / 75;
bbhx = -bbly + pageHei * 300 / 75;
bbly = t1 + pageMargin*2 * 300 / 75; // this may not work because "pageMargin" is badly defined
bbhy = t2 + pageMargin*2 * 300 / 75;
}
if (bblx > bbhx) { double s = bblx; bblx = bbhx; bbhx = s; }
if (bbly > bbhy) { double s = bbly; bbly = bbhy; bbhy = s; }
bblx = bblx / (PSSCALE * 75.0) * 72.0 * (bblx>=0 ? 1 : -1);
bbly = bbly / (PSSCALE * 75.0) * 72.0 * (bbly>=0 ? 1 : -1);
bbhx = bbhx / (PSSCALE * 75.0) * 72.0 * (bbhx>=0 ? 1 : -1);
bbhy = bbhy / (PSSCALE * 75.0) * 72.0 * (bbhy>=0 ? 1 : -1);
// Increase the size of the bounding box by one "pixel" to
// prevent the edges from being obscured by some drawing tools
printWriter.println("%%BoundingBox: " + (int)(bblx-1) + " " + (int)(bbly-1) + " " + (int)(bbhx+1) + " " + (int)(bbhy+1));
printWriter.println("%%DocumentFonts: " + DEFAULTFONT);
printWriter.println("%%EndComments");
if (!localPrefs.printEncapsulate) printWriter.println("%%Page: 1 1");
// PostScript: add some debugging info
if (cell != null)
{
Rectangle2D bounds = cell.getBounds();
printWriter.println("% cell dimensions: " + bounds.getWidth() + " wide x " + bounds.getHeight() + " high (database units)");
printWriter.println("% origin: " + bounds.getMinX() + " " + bounds.getMinY());
}
// disclaimers
if (localPrefs.printEncapsulate)
{
printWriter.println("% The EPS header should declare a private dictionary.");
} else
{
printWriter.println("% The non-EPS header does not claim conformance to Adobe-2.0");
printWriter.println("% because the structure may not be exactly correct.");
}
printWriter.println("%");
// set the page size if this is a plotter
if (localPrefs.printForPlotter)
{
printWriter.println("<< /PageSize [" + (int)(pageWid * 72 / 75) + " " + (int)(pageHei * 72 / 75) + "] >> setpagedevice");
}
// make the scale be exactly equal to one page pixel
printWriter.println("72 " + PSSCALE*75 + " div 72 " + PSSCALE*75 + " div scale");
// set the proper typeface
printWriter.println("/DefaultFont /" + DEFAULTFONT + " def");
printWriter.println("/scaleFont {");
printWriter.println(" DefaultFont findfont");
printWriter.println(" exch scalefont setfont} def");
// make the line width proper
lineWidth = (int)(PSSCALE/2 * localPrefs.printPSLineWidth);
printWriter.println(lineWidth + " setlinewidth");
// make the line ends look right
printWriter.println("1 setlinecap");
// rotate the image if requested
if (rotatePlot)
{
if (localPrefs.printForPlotter)
{
printWriter.println((pageWid/75) + " 300 mul " + ((pageHei-pageWid)/2/75) + " 300 mul translate 90 rotate");
} else
{
printWriter.println((pageHei+pageWid)/2/75 + " 300 mul " + (pageHei-pageWid)/2/75 + " 300 mul translate 90 rotate");
}
}
// fill background color if in color mode
if (psUseColor)
{
// color: emit the background color
PolyBase poly = new PolyBase(localPrefs.printBounds);
setColor(localPrefs.gp.getColor(ColorPrefType.BACKGROUND));
Point2D[] points = poly.getPoints();
putPSHeader(HEADERPOLYGON);
printWriter.print("[");
for(int k=0; k<points.length; k++)
{
if (k != 0) printWriter.print(" ");
Point2D ps = psXform(points[k]);
printWriter.print(TextUtils.formatDouble(ps.getX()) + " " + TextUtils.formatDouble(ps.getY()));
}
printWriter.println("] Polygon fill");
}
// initialize list of EGraphics modules that have been put out
patternsEmitted = new HashMap<EGraphics,Integer>();
psNumPatternsEmitted = 0;
return true;
}
/**
* Method to clean-up writing a cell.
*/
private void done()
{
// draw the grid if requested
if (psUseColor) printWriter.println("0 0 0 setrgbcolor");
if (localPrefs.wnd != null && localPrefs.isGrid)
{
int gridx = (int)localPrefs.gridXSpacing;
int gridy = (int)localPrefs.gridYSpacing;
int lx = (int)cell.getBounds().getMinX();
int ly = (int)cell.getBounds().getMinY();
int hx = (int)cell.getBounds().getMaxX();
int hy = (int)cell.getBounds().getMaxY();
int gridlx = lx / gridx * gridx;
int gridly = ly / gridy * gridy;
// adjust to ensure that the first point is inside the range
if (gridlx > lx) gridlx -= (gridlx - lx) / gridx * gridx;
if (gridly > ly) gridly -= (gridly - ly) / gridy * gridy;
while (gridlx < lx) gridlx += gridx;
while (gridly < ly) gridly += gridy;
// PostScript: write the grid loop
double matrix00 = matrix.getScaleX();
double matrix01 = matrix.getShearX();
double matrix10 = matrix.getShearY();
double matrix11 = matrix.getScaleY();
double matrix20 = matrix.getTranslateX();
double matrix21 = matrix.getTranslateY();
printWriter.println(gridlx + " " + gridx + " " + hx);
printWriter.println("{");
printWriter.println(" " + gridly + " " + gridy + " " + hy); // x y
printWriter.println(" {");
printWriter.println(" dup 3 -1 roll dup dup"); // y y x x x
printWriter.println(" 5 1 roll 3 1 roll"); // x y x y x
printWriter.println(" " + matrix00 + " mul exch " + matrix10 + " mul add " + matrix20 + " add"); // x y x x'
printWriter.println(" 3 1 roll"); // x x' y x
printWriter.println(" " + matrix01 + " mul exch " + matrix11 + " mul add " + matrix21 + " add"); // x x' y'
printWriter.println(" newpath moveto 0 0 rlineto stroke");
printWriter.println(" } for");
printWriter.println("} for");
}
// draw frame if it is there
PostScriptFrame pf = new PostScriptFrame(cell, this);
pf.renderFrame();
// put out dates if requested
if (localPrefs.plotDates)
{
putPSHeader(HEADERSTRING);
printWriter.print("0 " + (2 * CORNERDATESIZE * PSSCALE) + " ");
writePSString("Cell: " + cell.describe(false));
printWriter.println(" " + (CORNERDATESIZE * PSSCALE) + " Botleftstring");
printWriter.print("0 " + (CORNERDATESIZE * PSSCALE) + " ");
writePSString("Created: " + TextUtils.formatDate(cell.getCreationDate()));
printWriter.println(" " + (CORNERDATESIZE * PSSCALE) + " Botleftstring");
printWriter.print("0 0 ");
writePSString("Revised: " + TextUtils.formatDate(cell.getRevisionDate()));
printWriter.println(" " + (CORNERDATESIZE * PSSCALE) + " Botleftstring");
}
printWriter.println("showpage");
printWriter.println("%%Trailer");
}
/**
* Class for rendering a cell frame to the PostScript.
* Extends Cell.FrameDescription and provides hooks for drawing to a Graphics.
*/
private static class PostScriptFrame extends Cell.FrameDescription
{
private PostScript writer;
/**
* Constructor for cell frame rendering.
* @param cell the Cell that is having a frame drawn.
* @param writer the PostScript object for access to field variables.
*/
public PostScriptFrame(Cell cell, PostScript writer)
{
super(cell, 0);
this.writer = writer;
}
/**
* Method to draw a line in a frame.
* @param from the starting point of the line (in database units).
* @param to the ending point of the line (in database units).
*/
public void showFrameLine(Point2D from, Point2D to)
{
writer.psLine(from, to, 0);
}
/**
* Method to draw text in a frame.
* @param ctr the anchor point of the text.
* @param size the size of the text (in database units).
* @param maxWid the maximum width of the text (ignored if zero).
* @param maxHei the maximum height of the text (ignored if zero).
* @param string the text to be displayed.
*/
public void showFrameText(Point2D ctr, double size, double maxWid, double maxHei, String string)
{
Poly poly = null;
if (maxWid > 0 && maxHei > 0)
{
poly = new Poly(ctr.getX(), ctr.getY(), maxWid, maxHei);
poly.setStyle(Poly.Type.TEXTBOX);
} else
{
Point2D [] points = new Point2D[1];
points[0] = ctr;
poly = new Poly(points);
poly.setStyle(Poly.Type.TEXTCENT);
}
poly.setString(string);
TextDescriptor td = TextDescriptor.getNodeTextDescriptor().withRelSize(size * 0.75);
poly.setTextDescriptor(td);
writer.psText(poly);
}
}
/****************************** TRAVERSING THE HIERARCHY ******************************/
/**
* Method to write the body of the PostScript.
*/
private void scanCircuit()
{
lastColor = -1;
if (localPrefs.override != null)
{
for (PolyBase poly : localPrefs.override)
{
Point2D [] pts = poly.getPoints();
for(int i=0; i<pts.length; i++)
poly.setPoint(i, pts[i].getX(), localPrefs.printBounds.getHeight() - pts[i].getY());
}
}
// figure out the size of the job for progress display
Job.getUserInterface().startProgressDialog("Writing PostScript", null);
Job.getUserInterface().setProgressNote("Counting PostScript objects...");
long totalObjects = recurseCircuitLevel(cell, DBMath.MATID, true, false, 0);
if (psUseColor)
{
// color: plot layers in proper order
List<Layer> layerList = cell.getTechnology().getLayersSortedByHeight();
for(Layer layer : layerList)
{
if (localPrefs.invisibleLayers.contains(layer)) continue;
Job.getUserInterface().setProgressNote("Writing layer " + layer.getName() + " (" + totalObjects + " objects...");
currentLayer = layer.getIndex() + 1;
recurseCircuitLevel(cell, DBMath.MATID, true, true, totalObjects);
}
currentLayer = 0;
Job.getUserInterface().setProgressNote("Writing cell information (" + totalObjects + " objects...");
recurseCircuitLevel(cell, DBMath.MATID, true, true, totalObjects);
} else
{
// gray-scale: just plot it once
currentLayer = -1;
Job.getUserInterface().setProgressNote("Found " + totalObjects + " PostScript objects...");
recurseCircuitLevel(cell, DBMath.MATID, true, true, totalObjects);
}
Job.getUserInterface().stopProgressDialog();
}
/**
* Method to recursively write a Cell to the PostScript file.
* @param cell the Cell to write.
* @param trans the transformation matrix from the Cell to the top level.
* @param topLevel true if this is the top level.
* @param real true to really write PostScript (false when counting layers).
* @param progressTotal nonzero to display progress (in which case, this is the total).
* @return the number of objects processed.
*/
private int recurseCircuitLevel(Cell cell, AffineTransform trans, boolean topLevel, boolean real, long progressTotal)
{
int numObjects = 0;
if (localPrefs.override != null)
{
for (PolyBase poly : localPrefs.override)
{
if (real) psPoly(poly);
numObjects++;
if (progressTotal != 0 && (numObjects%100) == 0) {
long pct = numObjects*100/progressTotal;
Job.getUserInterface().setProgressValue((int)pct);
}
}
return numObjects;
}
// write the nodes
for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
AffineTransform subRot = ni.rotateOut();
subRot.preConcatenate(trans);
if (!ni.isCellInstance())
{
if (!topLevel)
{
if (ni.isVisInside()) continue;
if (ni.getProto() == Generic.tech().essentialBoundsNode ||
ni.getProto() == Generic.tech().cellCenterNode) continue;
}
if (real)
{
PrimitiveNode prim = (PrimitiveNode)ni.getProto();
Technology tech = prim.getTechnology();
Poly [] polys = tech.getShapeOfNode(ni);
for (int i=0; i<polys.length; i++)
{
polys[i].transform(subRot);
psPoly(polys[i]);
}
}
numObjects++;
if (progressTotal != 0 && (numObjects%100) == 0) {
long pct = numObjects*100/progressTotal;
Job.getUserInterface().setProgressValue((int)pct);
}
} else
{
// a cell instance
Cell subCell = (Cell)ni.getProto();
AffineTransform subTrans = ni.translateOut();
subTrans.preConcatenate(subRot);
if (!ni.isExpanded())
{
if (real)
{
Rectangle2D bounds = subCell.getBounds();
Poly poly = new Poly(bounds.getCenterX(), bounds.getCenterY(), ni.getXSize(), ni.getYSize());
poly.transform(subTrans);
poly.setStyle(Poly.Type.CLOSED);
poly.setLayer(null);
poly.setGraphicsOverride(blackGraphics);
// poly.setLayer(blackLayer);
psPoly(poly);
// Only when the instance names flag is on
if (localPrefs.gp.isTextVisibilityOn(TextDescriptor.TextType.INSTANCE))
{
poly.setStyle(Poly.Type.TEXTBOX);
TextDescriptor td = TextDescriptor.getInstanceTextDescriptor().withAbsSize(24);
poly.setTextDescriptor(td);
poly.setString(ni.getProto().describe(false));
psPoly(poly);
}
if (topLevel) showCellPorts(ni, trans, null);
}
numObjects++;
if (progressTotal != 0 && (numObjects%100) == 0) {
long pct = numObjects*100/progressTotal;
Job.getUserInterface().setProgressValue((int)pct);
}
} else
{
recurseCircuitLevel(subCell, subTrans, false, real, progressTotal);
if (topLevel && real) showCellPorts(ni, trans, Color.BLACK);
}
}
// draw any displayable variables on the node
if (/* topLevel && */ real && localPrefs.gp.isTextVisibilityOn(TextDescriptor.TextType.NODE))
{
Poly [] textPolys = ni.getDisplayableVariables(localPrefs.wnd, localPrefs.gp.isShowTempNames());
for (int i=0; i<textPolys.length; i++)
{
textPolys[i].transform(subRot);
psPoly(textPolys[i]);
}
}
// draw any exports from the node
if (topLevel && localPrefs.gp.isTextVisibilityOn(TextDescriptor.TextType.EXPORT))
{
for(Iterator<Export> eIt = ni.getExports(); eIt.hasNext(); )
{
Export e = eIt.next();
if (real)
{
Poly poly = e.getNamePoly();
if (localPrefs.gp.exportDisplayLevel == 2)
{
// draw port as a cross
drawCross(poly.getCenterX(), poly.getCenterY(), false);
} else
{
// draw port as text
if (localPrefs.gp.exportDisplayLevel == 1)
{
// use shorter port name
String portName = e.getShortName();
poly.setString(portName);
}
// rotate the descriptor
TextDescriptor descript = poly.getTextDescriptor();
Poly.Type style = descript.getPos().getPolyType();
style = Poly.rotateType(style, ni);
poly.setStyle(style);
psPoly(poly);
}
// draw variables on the export
Rectangle2D rect = (Rectangle2D)poly.getBounds2D().clone();
Poly[] polys = e.getDisplayableVariables(rect, localPrefs.wnd, true, localPrefs.gp.isShowTempNames());
for (int i=0; i<polys.length; i++)
{
psPoly(polys[i]);
}
}
numObjects++;
if (progressTotal != 0 && (numObjects%100) == 0) {
long pct = numObjects*100/progressTotal;
Job.getUserInterface().setProgressValue((int)pct);
}
}
}
}
// write the arcs
for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext();)
{
ArcInst ai = it.next();
if (real)
{
Technology tech = ai.getProto().getTechnology();
Poly[] polys = tech.getShapeOfArc(ai);
for (int i=0; i<polys.length; i++)
{
polys[i].transform(trans);
psPoly(polys[i]);
}
// draw any displayable variables on the arc
if (topLevel && localPrefs.gp.isTextVisibilityOn(TextDescriptor.TextType.ARC))
{
Poly[] textPolys = ai.getDisplayableVariables(localPrefs.wnd, localPrefs.gp.isShowTempNames());
for (int i=0; i<textPolys.length; i++)
{
textPolys[i].transform(trans);
psPoly(textPolys[i]);
}
}
}
numObjects++;
if (progressTotal != 0 && (numObjects%100) == 0) {
long pct = numObjects*100/progressTotal;
Job.getUserInterface().setProgressValue((int)pct);
}
}
// show cell variables if at the top level
if (topLevel && real && localPrefs.gp.isTextVisibilityOn(TextDescriptor.TextType.CELL))
{
// show displayable variables on the instance
Rectangle2D CENTERRECT = new Rectangle2D.Double(0, 0, 0, 0);
Poly[] polys = cell.getDisplayableVariables(CENTERRECT, localPrefs.wnd, true, localPrefs.gp.isShowTempNames());
for (int i=0; i<polys.length; i++)
psPoly(polys[i]);
}
return numObjects;
}
private void showCellPorts(NodeInst ni, AffineTransform trans, Color col)
{
// show the ports that are not further exported or connected
int numPorts = ni.getProto().getNumPorts();
boolean[] shownPorts = new boolean[numPorts];
for(Iterator<Connection> it = ni.getConnections(); it.hasNext();)
{
Connection con = it.next();
PortInst pi = con.getPortInst();
shownPorts[pi.getPortIndex()] = true;
}
for(Iterator<Export> it = ni.getExports(); it.hasNext();)
{
Export exp = it.next();
PortInst pi = exp.getOriginalPort();
shownPorts[pi.getPortIndex()] = true;
}
for(int i = 0; i < numPorts; i++)
{
if (shownPorts[i]) continue;
Export pp = (Export)ni.getProto().getPort(i);
Poly portPoly = ni.getShapeOfPort(pp);
if (portPoly == null) continue;
portPoly.transform(trans);
Color portColor = col;
if (portColor == null) portColor = pp.getBasePort().getPortColor(localPrefs.gp);
setColor(portColor);
if (localPrefs.gp.portDisplayLevel == 2)
{
// draw port as a cross
drawCross(portPoly.getCenterX(), portPoly.getCenterY(), false);
} else
{
// draw port as text
if (localPrefs.gp.isTextVisibilityOn(TextDescriptor.TextType.PORT))
{
// combine all features of port text with color of the port
TextDescriptor descript = portPoly.getTextDescriptor();
if (descript == null)
descript = TextDescriptor.TextType.EXPORT.getFactoryTextDescriptor();//TextDescriptor.EMPTY;
TextDescriptor portDescript = pp.getTextDescriptor(Export.EXPORT_NAME).withColorIndex(descript.getColorIndex());
Poly.Type type = descript.getPos().getPolyType();
portPoly.setStyle(type);
String portName = pp.getName();
if (localPrefs.gp.portDisplayLevel == 1)
{
// use shorter port name
portName = pp.getShortName();
}
portPoly.setString(portName);
portPoly.setTextDescriptor(portDescript);
psText(portPoly);
}
}
}
}
/****************************** EPS SYNCHRONIZATION ******************************/
/**
* Method to synchronize all PostScript files that need it.
* Examines all of the synchronization paths in all libraries.
* If they exist, the user is asked if synchronization should be done.
*/
public static boolean syncAll()
{
// see if there are synchronization links
boolean syncOther = false;
for(Iterator<Library> lIt = Library.getLibraries(); lIt.hasNext(); )
{
Library lib = lIt.next();
for(Iterator<Cell> cIt = lib.getCells(); cIt.hasNext(); )
{
Cell aCell = cIt.next();
Variable var = aCell.getVar(IOTool.POSTSCRIPT_FILENAME);
if (var != null)
{
String fileName = (String)var.getObject();
if (fileName.trim().length() > 0)
{
syncOther = true;
break;
}
}
}
if (syncOther) break;
}
if (syncOther)
{
String [] options = {"Yes", "No"};
int ret = Job.getUserInterface().askForChoice("Would you like to synchronize all PostScript drawings?",
"Synchronize EPS files", options, options[1]);
if (ret == 1) syncOther = false;
}
if (syncOther)
{
synchronizeEPSFiles();
return true;
}
return false;
}
private static boolean synchronizeEPSFiles()
{
// synchronize all cells
PostScriptPreferences psp = new PostScriptPreferences(false, null);
int numSyncs = 0;
for(Iterator<Library> lIt = Library.getLibraries(); lIt.hasNext(); )
{
Library oLib = lIt.next();
for(Iterator<Cell> cIt = oLib.getCells(); cIt.hasNext(); )
{
Cell oCell = cIt.next();
String syncFileName = IOTool.getPrintEPSSynchronizeFile(oCell);
if (syncFileName.length() == 0) continue;
// existing file: check the date to see if it should be overwritten
Date lastSavedDate = IOTool.getPrintEPSSavedDate(oCell);
if (lastSavedDate != null)
{
Date lastChangeDate = oCell.getRevisionDate();
if (lastSavedDate.after(lastChangeDate)) continue;
}
PostScript out = new PostScript(psp, oCell);
boolean err = out.writeCellToFile(syncFileName);
if (err) return true;
// mark the synchronized date
IOTool.setPrintEPSSavedDate(oCell, new Date());
numSyncs++;
}
}
if (numSyncs == 0)
System.out.println("No PostScript files needed to be written");
return false;
}
/****************************** POSTSCRIPT OUTPUT METHODS ******************************/
/**
* Method to set the PostScript color.
* @param col the color to write.
*/
private void setColor(Color col)
{
if (psUseColor)
{
if (col.getRGB() != lastColor)
{
lastColor = col.getRGB();
printWriter.println(col.getRed()/255.0f + " " + col.getGreen()/255.0f + " " + col.getBlue()/255.0f + " setrgbcolor");
}
}
}
/**
* Method to plot a polygon.
* @param poly the polygon to plot.
*/
private void psPoly(PolyBase poly)
{
// ignore null layers
Layer layer = poly.getLayer();
EGraphics gra = null;
int index = 0;
Color col = Color.BLACK;
Technology tech = cell.getTechnology();
if (layer != null)
{
tech = layer.getTechnology();
index = layer.getIndex();
if (localPrefs.invisibleLayers.contains(layer)) return;
if (poly instanceof Poly)
gra = ((Poly)poly).getGraphicsOverride();
if (gra == null)
gra = localPrefs.gp.getGraphics(layer);
col = gra.getColor();
}
// ignore layers that are not supposed to be dumped at this time
if (currentLayer >= 0)
{
if (currentLayer == 0)
{
if (tech == cell.getTechnology()) return;
} else
{
if (tech != cell.getTechnology() || currentLayer-1 != index)
return;
}
}
// set color if requested
setColor(col);
Poly.Type type = poly.getStyle();
Point2D [] points = poly.getPoints();
if (type == Poly.Type.FILLED)
{
Rectangle2D polyBox = poly.getBox();
if (polyBox != null)
{
if (polyBox.getWidth() == 0)
{
if (polyBox.getHeight() == 0)
{
psDot(new Point2D.Double(polyBox.getCenterX(), polyBox.getCenterY()));
} else
{
psLine(new Point2D.Double(polyBox.getCenterX(), polyBox.getMinY()),
new Point2D.Double(polyBox.getCenterX(), polyBox.getMaxY()), 0);
}
return;
} else if (polyBox.getHeight() == 0)
{
psLine(new Point2D.Double(polyBox.getMinX(), polyBox.getCenterY()),
new Point2D.Double(polyBox.getMaxX(), polyBox.getCenterY()), 0);
return;
}
psPolygon(poly);
return;
}
if (points.length == 1)
{
psDot(points[0]);
return;
}
if (points.length == 2)
{
psLine(points[0], points[1], 0);
return;
}
psPolygon(poly);
return;
}
if (type == Poly.Type.CLOSED)
{
Point2D lastPt = points[points.length-1];
for (int k = 0; k < points.length; k++)
{
psLine(lastPt, points[k], 0);
lastPt = points[k];
}
return;
}
if (type == Poly.Type.OPENED || type == Poly.Type.OPENEDT1 ||
type == Poly.Type.OPENEDT2 || type == Poly.Type.OPENEDT3)
{
int lineType = 0;
if (type == Poly.Type.OPENEDT1) lineType = 1; else
if (type == Poly.Type.OPENEDT2) lineType = 2; else
if (type == Poly.Type.OPENEDT3) lineType = 3;
for (int k = 1; k < points.length; k++)
psLine(points[k-1], points[k], lineType);
return;
}
if (type == Poly.Type.VECTORS)
{
for(int k=0; k<points.length; k += 2)
psLine(points[k], points[k+1], 0);
return;
}
if (type == Poly.Type.CROSS || type == Poly.Type.BIGCROSS)
{
double x = poly.getCenterX();
double y = poly.getCenterY();
drawCross(x, y, type == Poly.Type.BIGCROSS);
return;
}
if (type == Poly.Type.CROSSED)
{
Rectangle2D bounds = poly.getBounds2D();
psLine(new Point2D.Double(bounds.getMinX(), bounds.getMinY()), new Point2D.Double(bounds.getMinX(), bounds.getMaxY()), 0);
psLine(new Point2D.Double(bounds.getMinX(), bounds.getMaxY()), new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()), 0);
psLine(new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()), new Point2D.Double(bounds.getMaxX(), bounds.getMinY()), 0);
psLine(new Point2D.Double(bounds.getMaxX(), bounds.getMinY()), new Point2D.Double(bounds.getMinX(), bounds.getMinY()), 0);
psLine(new Point2D.Double(bounds.getMinX(), bounds.getMinY()), new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()), 0);
psLine(new Point2D.Double(bounds.getMinX(), bounds.getMaxY()), new Point2D.Double(bounds.getMaxX(), bounds.getMinY()), 0);
return;
}
if (type == Poly.Type.DISC)
{
psDisc(points[0], points[1]);
type = Poly.Type.CIRCLE;
}
if (type == Poly.Type.CIRCLE || type == Poly.Type.THICKCIRCLE)
{
psCircle(points[0], points[1]);
return;
}
if (type == Poly.Type.CIRCLEARC || type == Poly.Type.THICKCIRCLEARC)
{
psArc(points[0], points[1], points[2]);
return;
}
// text
psText((Poly)poly);
}
/**
* Method to draw a cross.
* @param x X center of the cross.
* @param y Y Center of the cross.
* @param bigCross true for a big cross, false for a small one
*/
private void drawCross(double x, double y, boolean bigCross)
{
double amount = 0.25;
if (bigCross) amount = 0.5;
psLine(new Point2D.Double(x-amount, y), new Point2D.Double(x+amount, y), 0);
psLine(new Point2D.Double(x, y+amount), new Point2D.Double(x, y-amount), 0);
}
/**
* Method to draw a dot
* @param pt the center of the dot.
*/
private void psDot(Point2D pt)
{
Point2D ps = psXform(pt);
putPSHeader(HEADERDOT);
printWriter.println(TextUtils.formatDouble(ps.getX()) + " " + TextUtils.formatDouble(ps.getY()) + " Putdot");
}
/**
* Method to draw a line.
* @param from the starting point of the line.
* @param to the ending point of the line.
* @param pattern the line texture (0 for solid, positive for dot/dash patterns).
*/
private void psLine(Point2D from, Point2D to, int pattern)
{
Point2D pt1 = psXform(from);
Point2D pt2 = psXform(to);
putPSHeader(HEADERLINE);
int i = PSSCALE / 2;
switch (pattern)
{
case 0:
printWriter.println(TextUtils.formatDouble(pt1.getX()) + " " + TextUtils.formatDouble(pt1.getY()) + " " +
TextUtils.formatDouble(pt2.getX()) + " " + TextUtils.formatDouble(pt2.getY()) + " Drawline");
break;
case 1:
printWriter.print("[" + i + " " + i*3 + "] 0 setdash ");
printWriter.println(TextUtils.formatDouble(pt1.getX()) + " " + TextUtils.formatDouble(pt1.getY()) + " " +
TextUtils.formatDouble(pt2.getX()) + " " + TextUtils.formatDouble(pt2.getY()) + " Drawline");
printWriter.println(" [] 0 setdash");
break;
case 2:
printWriter.print("[" + i*6 + " " + i*3 + "] 0 setdash ");
printWriter.println(TextUtils.formatDouble(pt1.getX()) + " " + TextUtils.formatDouble(pt1.getY()) + " " +
TextUtils.formatDouble(pt2.getX()) + " " + TextUtils.formatDouble(pt2.getY()) + " Drawline");
printWriter.println(" [] 0 setdash");
break;
case 3:
printWriter.print((lineWidth*2) + " setlinewidth ");
printWriter.println(TextUtils.formatDouble(pt1.getX()) + " " + TextUtils.formatDouble(pt1.getY()) + " " +
TextUtils.formatDouble(pt2.getX()) + " " + TextUtils.formatDouble(pt2.getY()) + " Drawline");
printWriter.println(lineWidth + " setlinewidth");
break;
}
}
/**
* Method to draw an arc of a circle.
* @param center the center of the arc's circle.
* @param pt1 the starting point of the arc.
* @param pt2 the ending point of the arc.
*/
private void psArc(Point2D center, Point2D pt1, Point2D pt2)
{
Point2D pc = psXform(center);
Point2D ps1 = psXform(pt1);
Point2D ps2 = psXform(pt2);
double radius = pc.distance(ps1);
int startAngle = (DBMath.figureAngle(pc, ps2) + 5) / 10;
int endAngle = (DBMath.figureAngle(pc, ps1) + 5) / 10;
printWriter.println("newpath " + TextUtils.formatDouble(pc.getX()) + " " + TextUtils.formatDouble(pc.getY()) + " " + radius + " " +
startAngle + " " + endAngle + " arc stroke");
}
/**
* Method to draw an unfilled circle.
* @param center the center of the circle.
* @param pt a point on the circle.
*/
private void psCircle(Point2D center, Point2D pt)
{
Point2D pc = psXform(center);
Point2D ps = psXform(pt);
double radius = pc.distance(ps);
printWriter.println("newpath " + TextUtils.formatDouble(pc.getX()) + " " + TextUtils.formatDouble(pc.getY()) + " " + radius + " 0 360 arc stroke");
}
/**
* Method to draw a filled circle.
* @param center the center of the circle.
* @param pt a point on the circle.
*/
private void psDisc(Point2D center, Point2D pt)
{
Point2D pc = psXform(center);
Point2D ps = psXform(pt);
double radius = pc.distance(ps);
printWriter.println("newpath " + TextUtils.formatDouble(pc.getX()) + " " + TextUtils.formatDouble(pc.getY()) + " " + radius + " 0 360 arc fill");
}
/**
* Method to draw an irregular polygon.
* @param poly the polygon to draw.
*/
private void psPolygon(PolyBase poly)
{
Point2D [] points = poly.getPoints();
if (points.length == 0) return;
// // ignore if too small
// Rectangle2D polyBounds = null;
// for(int i=0; i<points.length; i++)
// {
// Point2D pu = psXform(points[i]);
// if (polyBounds == null) polyBounds = new Rectangle2D.Double(pu.getX(), pu.getY(), 0, 0); else
// polyBounds.add(pu);
// }
// if (polyBounds.getWidth() < 1 || polyBounds.getHeight() < 1) return;
EGraphics desc = null;
if (poly instanceof Poly)
desc = ((Poly)poly).getGraphicsOverride();
if (desc == null)
desc = localPrefs.gp.getGraphics(poly.getLayer());
// use solid color if solid pattern or no pattern
boolean stipplePattern = desc.isPatternedOnPrinter();
// if stipple pattern is solid, just use solid fill
if (stipplePattern)
{
int [] pattern = desc.getPattern();
boolean solid = true;
for(int i=0; i<8; i++)
if (pattern[i] != 0xFFFF) { solid = false; break; }
if (solid) stipplePattern = false;
}
// put out solid fill if appropriate
if (!stipplePattern)
{
putPSHeader(HEADERPOLYGON);
printWriter.print("[");
for(int i=0; i<points.length; i++)
{
if (i != 0) printWriter.print(" ");
Point2D ps = psXform(points[i]);
printWriter.print(TextUtils.formatDouble(ps.getX()) + " " + TextUtils.formatDouble(ps.getY()));
}
printWriter.println("] Polygon fill");
return;
}
/*
* patterned fill: the hard one
* Generate filled polygons by defining a stipple font and then tiling the
* polygon to fill with 128x128 pixel characters, clipping to the polygon edge.
*/
putPSHeader(HEADERPOLYGON);
putPSHeader(HEADERFPOLYGON);
printWriter.print("(" + psPattern(desc) + ") [");
Point2D ps = psXform(points[0]);
double lx = ps.getX();
double hx = lx;
double ly = ps.getY();
double hy = ly;
for(int i=0; i<points.length; i++)
{
if (i != 0) printWriter.print(" ");
Point2D psi = psXform(points[i]);
printWriter.print(psi.getX() + " " + psi.getY());
if (psi.getX() < lx) lx = psi.getX(); if (psi.getX() > hx) hx = psi.getX();
if (psi.getY() < ly) ly = psi.getY(); if (psi.getY() > hy) hy = psi.getY();
}
printWriter.println("] " + TextUtils.formatDouble(hx-lx+1) + " " + TextUtils.formatDouble(hy-ly+1) + " " +
TextUtils.formatDouble(lx) + " " + TextUtils.formatDouble(ly) + " Filledpolygon");
}
/**
* Method to draw text.
* @param poly the text polygon to draw.
*/
private void psText(Poly poly)
{
Poly.Type style = poly.getStyle();
TextDescriptor td = poly.getTextDescriptor();
if (td == null) return;
int size = (int)(td.getTrueSize(localPrefs.wnd) * PSTEXTSCALE * PSSCALE);
Rectangle2D bounds = poly.getBounds2D();
// get the font size
if (size <= 0) return;
// make sure the string is valid
String text = poly.getString().trim();
if (text.length() == 0) return;
// write header information
Point2D psL = psXform(new Point2D.Double(bounds.getMinX(), bounds.getMinY()));
Point2D psH = psXform(new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()));
double cX = (psL.getX() + psH.getX()) / 2;
double cY = (psL.getY() + psH.getY()) / 2;
double sX = Math.abs(psH.getX() - psL.getX());
double sY = Math.abs(psH.getY() - psL.getY());
putPSHeader(HEADERSTRING);
// set text color
Color full;
int index = td.getColorIndex();
if (index == 0) full = localPrefs.gp.getColor(ColorPrefType.TEXT); else
{
Color [] map = localPrefs.gp.getColorMap(cell.getTechnology());
full = EGraphics.getColorFromIndex(index, map);
}
setColor(full);
boolean changedFont = false;
String faceName = null;
int faceNumber = td.getFace();
if (faceNumber != 0)
{
TextDescriptor.ActiveFont af = TextDescriptor.ActiveFont.findActiveFont(faceNumber);
if (af != null) faceName = af.getName();
}
if (faceName != null)
{
String fixedFaceName = faceName.replace(' ', '-');
printWriter.println("/DefaultFont /" + fixedFaceName + " def");
changedFont = true;
} else
{
if (td.isItalic())
{
if (td.isBold())
{
printWriter.println("/DefaultFont /" + DEFAULTFONTBI + " def");
changedFont = true;
} else
{
printWriter.println("/DefaultFont /" + DEFAULTFONTITALIC + " def");
changedFont = true;
}
} else if (td.isBold())
{
printWriter.println("/DefaultFont /" + DEFAULTFONTBOLD + " def");
changedFont = true;
}
}
if (poly.getStyle() == Poly.Type.TEXTBOX)
{
printWriter.print(TextUtils.formatDouble(cX) + " " + TextUtils.formatDouble(cY) + " " +
TextUtils.formatDouble(sX) + " " + TextUtils.formatDouble(sY) + " ");
writePSString(text);
printWriter.println(" " + size + " Boxstring");
} else
{
String opName = null;
double x = 0, y = 0;
if (style == Poly.Type.TEXTCENT)
{
x = cX; y = cY;
opName = "Centerstring";
} else if (style == Poly.Type.TEXTTOP)
{
x = cX; y = psH.getY();
opName = "Topstring";
} else if (style == Poly.Type.TEXTBOT)
{
x = cX; y = psL.getY();
opName = "Botstring";
} else if (style == Poly.Type.TEXTLEFT)
{
x = psL.getX(); y = cY;
opName = "Leftstring";
} else if (style == Poly.Type.TEXTRIGHT)
{
x = psH.getX(); y = cY;
opName = "Rightstring";
} else if (style == Poly.Type.TEXTTOPLEFT)
{
x = psL.getX(); y = psH.getY();
opName = "Topleftstring";
} else if (style == Poly.Type.TEXTTOPRIGHT)
{
x = psH.getX(); y = psH.getY();
opName = "Toprightstring";
} else if (style == Poly.Type.TEXTBOTLEFT)
{
x = psL.getX(); y = psL.getY();
opName = "Botleftstring";
} else if (style == Poly.Type.TEXTBOTRIGHT)
{
x = psH.getX(); y = psL.getY();
opName = "Botrightstring";
}
int xoff = (int)x, yoff = (int)y;
double descenderoffset = size / 12;
TextDescriptor.Rotation rot = td.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;
if (rot != TextDescriptor.Rotation.ROT0)
{
if (rot == TextDescriptor.Rotation.ROT90 || rot == TextDescriptor.Rotation.ROT270)
{
if (style == Poly.Type.TEXTTOP) opName = "Rightstring"; else
if (style == Poly.Type.TEXTBOT) opName = "Leftstring"; else
if (style == Poly.Type.TEXTLEFT) opName = "Botstring"; else
if (style == Poly.Type.TEXTRIGHT) opName = "Topstring"; else
if (style == Poly.Type.TEXTTOPLEFT) opName = "Botrightstring"; else
if (style == Poly.Type.TEXTBOTRIGHT) opName = "Topleftstring";
}
x = y = 0;
if (rot == TextDescriptor.Rotation.ROT90)
{
// 90 degrees counterclockwise
printWriter.println(xoff + " " + yoff + " translate 90 rotate");
} else if (rot == TextDescriptor.Rotation.ROT180)
{
// 180 degrees
printWriter.println(xoff + " " + yoff + " translate 180 rotate");
} else if (rot == TextDescriptor.Rotation.ROT270)
{
// 90 degrees clockwise
printWriter.println(xoff + " " + yoff + " translate 270 rotate");
}
}
printWriter.print(TextUtils.formatDouble(x) + " " + TextUtils.formatDouble(y) + " ");
writePSString(text);
printWriter.println(" " + size + " " + opName);
if (rot != TextDescriptor.Rotation.ROT0)
{
if (rot == TextDescriptor.Rotation.ROT90)
{
// 90 degrees counterclockwise
printWriter.println("-90 rotate " + (-xoff) + " " + (-yoff) + " translate");
} else if (rot == TextDescriptor.Rotation.ROT180)
{
// 180 degrees
printWriter.println("-180 rotate " + (-xoff) + " " + (-yoff) + " translate");
} else if (rot == TextDescriptor.Rotation.ROT270)
{
// 90 degrees clockwise
printWriter.println("-270 rotate " + (-xoff) + " " + (-yoff) + " translate");
}
}
}
if (changedFont)
{
printWriter.println("/DefaultFont /" + DEFAULTFONT + " def");
}
}
/****************************** SUPPORT ******************************/
private String [] headerDot =
{
"/Putdot {", // print dot at stack position
" newpath moveto 0 0 rlineto stroke} def"
};
private String [] headerLine =
{
"/Drawline {", // draw line on stack
" newpath moveto lineto stroke} def"
};
private String [] headerPolygon =
{
"/Polygon {", // put array into path
" aload",
" length 2 idiv /len exch def",
" newpath",
" moveto",
" len 1 sub {lineto} repeat",
" closepath",
"} def"
};
private String [] headerFilledPolygon =
{
"/BuildCharDict 10 dict def", // ref Making a User Defined (PostScript Cookbook)
"/StippleFont1 7 dict def",
"StippleFont1 begin",
" /FontType 3 def",
" /FontMatrix [1 0 0 1 0 0] def",
" /FontBBox [0 0 1 1] def",
" /Encoding 256 array def",
" 0 1 255 {Encoding exch /.notdef put} for",
" /CharacterDefs 40 dict def",
" CharacterDefs /.notdef {} put",
" /BuildChar",
" { BuildCharDict begin",
" /char exch def",
" /fontdict exch def",
" /charname fontdict /Encoding get",
" char get def",
" /charproc fontdict /CharacterDefs get",
" charname get def",
" 1 0 0 0 1 1 setcachedevice",
" gsave charproc grestore",
" end",
" } def",
"end",
"/StippleFont StippleFont1 definefont pop",
"/StippleCharYSize 128 def",
"/StippleCharXSize StippleCharYSize def",
"/Filledpolygon {",
" gsave",
" /StippleFont findfont StippleCharYSize scalefont setfont",
" /LowY exch def /LowX exch def",
" /HighY exch LowY add def /HighX exch LowX add def",
" Polygon clip",
" /Char exch def",
" /LowY LowY StippleCharYSize div truncate StippleCharYSize mul def",
" /LowX LowX StippleCharXSize div truncate StippleCharXSize mul def",
" /HighY HighY StippleCharYSize div 1 add truncate StippleCharYSize mul def",
" /HighX HighX StippleCharXSize div 1 add truncate StippleCharXSize mul def",
" LowY StippleCharYSize HighY ",
" { LowX exch moveto ",
" LowX StippleCharXSize HighX ",
" { Char show pop ",
" } for ",
" } for",
" grestore",
"} def"
};
String [] headerString =
{
/*
* Code to do super and subscripts:
*
* example:
* "NORMAL\dSUB\} NORMAL\ uSUP\}"
*
* will subscript "SUB" and superscript "SUP", so "\d" starts a
* subscript, "\ u" starts a superscript, "\}" returns to
* normal. Sub-subscripts, and super-superscripts are not
* supported. To print a "\", use "\\".
*
* changes:
*
* all calls to stringwidth were changed to calls to StringLength,
* which returns the same info (assumes non-rotated text), but
* takes sub- and super-scripts into account.
* all calls to show were changes to calls to StringShow, which
* handles sub- and super-scripts.
* note that TSize is set to the text height, and is passed to
* StringLength and StringShow.
*/
"/ComStart 92 def", // "\", enter command mode
"/ComSub 100 def", // "d", start subscript
"/ComSup 117 def", // "u", start superscript
"/ComNorm 125 def", // "}", return to normal
"/SSSize .70 def", // sub- and super-script size
"/SubDy -.20 def", // Dy for sub-script
"/SupDy .40 def", // Dy for super-script*/
"/StringShow {", // str size StringShow
" /ComMode 0 def", // command mode flag
" /TSize exch def", // text size
" /TString exch def", // string to draw
" /NormY currentpoint exch pop def", // save Y coordinate of string
" TSize scaleFont",
" TString {", // scan string char by char
" /CharCode exch def", // save char
" ComMode 1 eq {",
" /ComMode 0 def", // command mode
" CharCode ComSub eq {", // start subscript
" TSize SSSize mul scaleFont",
" currentpoint pop NormY TSize SubDy mul add moveto",
" } if",
" CharCode ComSup eq {", // start superscript
" TSize SSSize mul scaleFont",
" currentpoint pop NormY TSize SupDy mul add moveto",
" } if",
" CharCode ComNorm eq {", // end sub- or super-script
" TSize scaleFont",
" currentpoint pop NormY moveto",
" } if",
" CharCode ComStart eq {", // print a "\"
" ( ) dup 0 CharCode put show",
" } if",
" }",
" {",
" CharCode ComStart eq {",
" /ComMode 1 def", // enter command mode
" }",
" {",
" ( ) dup 0 CharCode put show", // print char
" } ifelse",
" } ifelse",
" } forall ",
"} def",
"/StringLength {", // str size StringLength
" /ComMode 0 def", // command mode flag
" /StrLen 0 def", // total string length
" /TSize exch def", // text size
" /TString exch def", // string to draw
" TSize scaleFont",
" TString {", // scan string char by char
" /CharCode exch def", // save char
" ComMode 1 eq {",
" /ComMode 0 def", // command mode
" CharCode ComSub eq {", // start subscript
" TSize SSSize mul scaleFont",
" } if",
" CharCode ComSup eq {", // start superscript
" TSize SSSize mul scaleFont",
" } if",
" CharCode ComNorm eq {", // end sub- or super-script
" TSize scaleFont",
" } if",
" CharCode ComStart eq {", // add "\" to length
" ( ) dup 0 CharCode put stringwidth pop StrLen add",
" /StrLen exch def",
" } if",
" }",
" {",
" CharCode ComStart eq {",
" /ComMode 1 def", // enter command mode
" }",
" {", // add char to length
" ( ) dup 0 CharCode put stringwidth pop StrLen add",
" /StrLen exch def",
" } ifelse",
" } ifelse",
" } forall ",
" StrLen 0", // return info like stringwidth
"} def",
"/Centerstring {", // x y str sca
" dup /TSize exch def", // save size
" dup scaleFont exch dup TSize StringLength", // x y sca str xw yw
" pop 3 -1 roll .5 mul", // x y str xw sca*.5 (was .8)
" exch 5 -1 roll exch 2 div sub", // y str sca*.5 x-xw/2
" exch 4 -1 roll exch 2 div sub", // str x-xw/2 y-sca*.5/2
" moveto TSize StringShow",
"} def",
"/Topstring {", // x y str sca
" dup /TSize exch def", // save size
" dup scaleFont exch dup TSize StringLength", // x y sca str xw yw
" pop 3 -1 roll .5 mul", // x y str xw sca*.5 (was .8)
" exch 5 -1 roll exch 2 div sub", // y str sca*.5 x-xw/2
" exch 4 -1 roll exch sub", // str x-xw/2 y-sca*.5
" moveto TSize StringShow",
"} def",
"/Botstring {", // x y str sca
" dup /TSize exch def", // save size
" scaleFont dup TSize StringLength pop", // x y str xw
" 4 -1 roll exch 2 div sub", // y str x-xw/2
" 3 -1 roll moveto TSize StringShow",
"} def",
"/Leftstring {", // x y str sca
" dup /TSize exch def", // save size
" dup scaleFont .4 mul", // x y str sca*.4
" 3 -1 roll exch sub", // x str y-sca*.4
" 3 -1 roll exch", // str x y-sca*.4
" moveto TSize StringShow",
"} def",
"/Rightstring {", // x y str sca
" dup /TSize exch def", // save size
" dup scaleFont exch dup TSize StringLength", // x y sca str xw yw
" pop 3 -1 roll .4 mul", // x y str xw sca*.4
" exch 5 -1 roll exch sub", // y str sca*.4 x-xw
" exch 4 -1 roll exch sub", // str x-xw y-sca*.4
" moveto TSize StringShow",
"} def",
"/Topleftstring {", // x y str sca
" dup /TSize exch def", // save size
" dup scaleFont .5 mul", // x y str sca*.5 (was .8)
" 3 -1 roll exch sub", // x str y-sca*.5
" 3 -1 roll exch", // str x y-sca*.5
" moveto TSize StringShow",
"} def",
"/Toprightstring {", // x y str sca
" dup /TSize exch def", // save size
" dup scaleFont exch dup TSize StringLength", // x y sca str xw yw
" pop 3 -1 roll .5 mul", // x y str xw sca*.5 (was .8)
" exch 5 -1 roll exch sub", // y str sca*.5 x-xw
" exch 4 -1 roll exch sub", // str x-xw y-sca*.5
" moveto TSize StringShow",
"} def",
"/Botleftstring {", // x y str sca
" dup /TSize exch def", // save size
" scaleFont 3 1 roll moveto TSize StringShow",
"} def",
"/Botrightstring {", // x y str sca
" dup /TSize exch def", // save size
" scaleFont dup TSize StringLength",
" pop 4 -1 roll exch",
" sub 3 -1 roll",
" moveto TSize StringShow",
"} def",
"/Min {", // leave minimum of top two
" dup 3 -1 roll dup",
" 3 1 roll gt",
" {exch} if pop",
"} def",
"/Boxstring {", // x y mx my str sca
" dup /TSize exch def", // save size
" dup scaleFont", // x y mx my str sca
" exch dup TSize StringLength pop", // x y mx my sca str xw
" 3 -1 roll dup", // x y mx my str xw sca sca
" 6 -1 roll mul", // x y my str xw sca sca*mx
" 3 -1 roll div", // x y my str sca sca*mx/xw
" 4 -1 roll", // x y str sca sca*mx/xw my
" Min Min", // x y str minsca
" Centerstring",
"} def"
};
/**
* Method to write boilerplate for different types of PostScript objects.
* @param which the desired object.
*/
private void putPSHeader(int which)
{
switch (which)
{
case HEADERDOT:
if (putHeaderDot) return;
putHeaderDot = true;
for(int i=0; i<headerDot.length; i++)
printWriter.println(headerDot[i]);
break;
case HEADERLINE:
if (putHeaderLine) return;
putHeaderLine = true;
for(int i=0; i<headerLine.length; i++)
printWriter.println(headerLine[i]);
break;
case HEADERPOLYGON:
if (putHeaderPolygon) return;
putHeaderPolygon = true;
for(int i=0; i<headerPolygon.length; i++)
printWriter.println(headerPolygon[i]);
break;
case HEADERFPOLYGON:
if (putHeaderFilledPolygon) return;
putHeaderFilledPolygon = true;
for(int i=0; i<headerFilledPolygon.length; i++)
printWriter.println(headerFilledPolygon[i]);
break;
case HEADERSTRING:
if (putHeaderString) return;
putHeaderString = true;
for(int i=0; i<headerString.length; i++)
printWriter.println(headerString[i]);
break;
}
}
/**
* Method to write a pattern.
* @param col the pattern to write.
* @return the letter associated with the pattern.
*/
private char psPattern(EGraphics col)
{
// see if this graphics has been seen already
Integer index = patternsEmitted.get(col);
if (index != null) return (char)('A' + index.intValue());
// add to list
patternsEmitted.put(col, new Integer(psNumPatternsEmitted));
int [] raster = col.getPattern();
char indexChar = (char)(psNumPatternsEmitted+'A');
psNumPatternsEmitted++;
/*
* Generate filled polygons by defining a stipple font,
* and then tiling the polygon to fill with 128x128 pixel
* characters, clipping to the polygon edge.
*
* Take Electric's 16x8 bit images, double each bit,
* and then output 4 times to get 128 bit wide image.
* Double vertically by outputting each row twice.
* Note that full vertical size need not be generated,
* as PostScript will just reuse the lines until the 128
* size is reached.
*
* see: "Making a User Defined Font", PostScript Cookbook
*/
printWriter.println("StippleFont1 begin");
printWriter.println(" Encoding (" + indexChar + ") 0 get /Stipple" + indexChar + " put");
printWriter.println(" CharacterDefs /Stipple" + indexChar + " {");
printWriter.println(" 128 128 true [128 0 0 -128 0 128]");
printWriter.println(" { <");
for(int i=0; i<8; i++)
{
int bl = raster[i] & 0x00FF;
int bh = (raster[i] & 0xFF00) >> 8;
int bld = 0, bhd = 0;
for (int k=0; k<8; ++k)
{
bld = (bld << 1);
bld |= (bl & 0x1);
bld = (bld << 1);
bld |= (bl & 0x1);
bl = (bl >> 1);
bhd = (bhd << 1);
bhd |= (bh & 0x1);
bhd = (bhd << 1);
bhd |= (bh & 0x1);
bh = (bh >> 1);
}
for (int k=0; k<2; k++)
{
printWriter.print(" ");
for(int j=0; j<4; j++)
printWriter.print((bhd&0xFFFF) + " " + (bld&0xFFFF) + " ");
printWriter.println();
}
}
printWriter.println(" > } imagemask");
printWriter.println(" } put");
printWriter.println("end");
return indexChar;
}
/**
* Method to convert coordinates for display.
* @param pt the Electric coordinates.
* @return the PostScript coordinates.
*/
private Point2D psXform(Point2D pt)
{
Point2D result = new Point2D.Double();
matrix.transform(pt, result);
return result;
}
/**
* Method to write PostScript text.
* @param str the text to write in PostScript format.
*/
public void writePSString(String str)
{
printWriter.print("(");
for(int i=0; i<str.length(); i++)
{
char ca = str.charAt(i);
if (ca == '(' || ca == ')' || ca == '\\') printWriter.print("\\");
printWriter.print(ca);
}
printWriter.print(")");
}
}