/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: SVG.java
* Input/output tool: Scalable Vector Graphics output
* Written by Steven M. Rubin, Sun Microsystems.
*
* Copyright (c) 2010, 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.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.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.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.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* This class writes files in SVG format.
*/
public class SVG extends Output
{
/** the Cell being written. */ private Cell cell;
/** current layer number (-1: do all; 0: cleanup). */ private int currentLayer;
/** matrix from database units to SVG 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});
private SVGPreferences localPrefs;
public static class SVGPreferences extends OutputPreferences
{
double scale = IOTool.getFactorySVGScale();
double printMargin = IOTool.getFactorySVGMargin();
GraphicsPreferences gp;
EditWindow0.EditWindowSmall wnd;
ERectangle printBounds;
Set<Layer> invisibleLayers = new HashSet<Layer>();
SVGPreferences(boolean factory)
{
super(factory);
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()
{
scale = IOTool.getSVGScale();
printMargin = IOTool.getSVGMargin();
UserInterface ui = Job.getUserInterface();
EditWindow_ localWnd = ui.getCurrentEditWindow_();
wnd = new EditWindow0.EditWindowSmall(localWnd);
// determine the area of interest
Cell cell = localWnd.getCell();
printBounds = ERectangle.fromLambda(getAreaToPrint(cell, false, localWnd));
}
public Output doOutput(Cell cell, VarContext context, String filePath)
{
SVG out = new SVG(this, cell);
out.writeCellToFile(filePath);
return out.finishWrite();
}
}
/**
* SVG constructor.
*/
private SVG(SVGPreferences pp, Cell cell)
{
localPrefs = pp;
this.cell = cell;
}
/**
* Internal method for SVG output.
* @param filePath the disk file to create.
*/
private boolean writeCellToFile(String filePath)
{
if (localPrefs.printBounds == null) localPrefs.printBounds = cell.getBounds();
boolean error = false;
if (openTextOutputStream(filePath)) error = true; else
{
// write out the 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()
{
// compute the transformation matrix
double i = localPrefs.scale;
double matrix00 = i; double matrix01 = 0;
double matrix10 = 0; double matrix11 = -i;
double matrix20 = -localPrefs.printBounds.getMinX()*i + localPrefs.printMargin;
double matrix21 = localPrefs.printBounds.getMaxY()*i + localPrefs.printMargin;
matrix = new AffineTransform(matrix00, matrix01, matrix10, matrix11, matrix20, matrix21);
// write SVG header
printWriter.println("<?xml version=\"1.0\" standalone=\"no\"?>");
printWriter.println("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
printWriter.println();
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("<!-- Date: " + TextUtils.formatDate(now) + "-->");
} else
{
printWriter.println("<!-- Creator: Electric VLSI Design System -->");
}
emitCopyright("<!-- ", "-->");
printWriter.println("<svg width=\"" + Integer.toString((int)(localPrefs.printBounds.getWidth()*i + localPrefs.printMargin*2)) +
"\" height=\"" + Integer.toString((int)(localPrefs.printBounds.getHeight()*i + localPrefs.printMargin*2)) + "\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">");
printWriter.println();
return true;
}
/**
* Method to clean-up writing a cell.
*/
private void done()
{
// draw frame if it is there
SVGFrame pf = new SVGFrame(cell, this);
pf.renderFrame();
printWriter.println();
printWriter.println("</svg>");
}
/**
* Class for rendering a cell frame to the SVG.
* Extends Cell.FrameDescription and provides hooks for drawing to a Graphics.
*/
private static class SVGFrame extends Cell.FrameDescription
{
private SVG writer;
/**
* Constructor for cell frame rendering.
* @param cell the Cell that is having a frame drawn.
* @param writer the SVG object for access to field variables.
*/
public SVGFrame(Cell cell, SVG 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.svgLine(from, to, Color.BLACK, 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.svgText(poly, Color.BLACK);
}
}
/****************************** TRAVERSING THE HIERARCHY ******************************/
/**
* Method to write the body of the SVG.
*/
private void scanCircuit()
{
// figure out the size of the job for progress display (and gather patterns)
Job.getUserInterface().startProgressDialog("Writing SVG", null);
Job.getUserInterface().setProgressNote("Counting SVG objects...");
printWriter.println("<defs>");
long totalObjects = recurseCircuitLevel(cell, DBMath.MATID, true, false, 0);
printWriter.println("</defs>");
// color: plot layers in proper order
List<Layer> layerList = cell.getTechnology().getLayersSortedByHeight();
List<Layer> contactLayers = new ArrayList<Layer>();
List<Layer> regularLayers = new ArrayList<Layer>();
for(Layer layer : layerList)
{
if (layer.getFunction().isContact()) contactLayers.add(layer); else
regularLayers.add(layer);
}
for(Layer layer : contactLayers) regularLayers.add(layer);
for(Layer layer : regularLayers)
{
if (localPrefs.invisibleLayers.contains(layer)) continue;
Job.getUserInterface().setProgressNote("Writing layer " + layer.getName() + " (" + totalObjects + " objects...");
EGraphics gra = localPrefs.gp.getGraphics(layer);
Color col = gra.getColor();
boolean opaque = gra.getTransparentLayer() == 0;
String patternName = null;
if (gra.isPatternedOnDisplay()) patternName = layer.getName();
printWriter.print("<g id=\"Layer-" + layer.getName() + "\"");
if (patternName != null) printWriter.print(" fill=\"url(#" + patternName + ")\""); else
{
printWriter.print(" fill=\"" + getColorDescription(col) + "\"");
if (!opaque) printWriter.print(" opacity=\"0.5\"");
}
printWriter.println(">");
currentLayer = layer.getIndex() + 1;
recurseCircuitLevel(cell, DBMath.MATID, true, true, totalObjects);
printWriter.println("</g>");
}
currentLayer = 0;
Job.getUserInterface().setProgressNote("Writing cell information (" + totalObjects + " objects...");
recurseCircuitLevel(cell, DBMath.MATID, true, true, totalObjects);
Job.getUserInterface().stopProgressDialog();
}
/**
* Method to recursively write a Cell to the SVG 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 SVG (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;
// 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 (ni.getProto() == Generic.tech().essentialBoundsNode ||
ni.getProto() == Generic.tech().cellCenterNode) continue;
if (!topLevel && ni.isVisInside()) continue;
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);
svgPoly(polys[i], real);
}
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())
{
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);
svgPoly(poly, real);
// Only when the instance names flag is on
if (real)
{
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));
svgPoly(poly, true);
}
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 (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);
svgPoly(textPolys[i], true);
}
}
// 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(), Color.BLACK, 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);
svgPoly(poly, true);
}
// 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++)
{
svgPoly(polys[i], true);
}
}
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();
Technology tech = ai.getProto().getTechnology();
Poly[] polys = tech.getShapeOfArc(ai);
for (int i=0; i<polys.length; i++)
{
polys[i].transform(trans);
svgPoly(polys[i], real);
}
if (real)
{
// 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);
svgPoly(textPolys[i], true);
}
}
}
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++)
svgPoly(polys[i], true);
}
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);
if (localPrefs.gp.portDisplayLevel == 2)
{
// draw port as a cross
drawCross(portPoly.getCenterX(), portPoly.getCenterY(), portColor, 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);
svgText(portPoly, portColor);
}
}
}
}
/****************************** SVG OUTPUT METHODS ******************************/
/**
* Method to plot a polygon.
* @param poly the polygon to plot.
*/
private void svgPoly(PolyBase poly, boolean real)
{
// ignore null layers
Layer layer = poly.getLayer();
EGraphics gra = null;
int index = 0;
Technology tech = cell.getTechnology();
if (layer != null)
{
tech = layer.getTechnology();
index = layer.getIndex();
if (localPrefs.invisibleLayers.contains(layer)) return;
if (!real || currentLayer == 0) gra = localPrefs.gp.getGraphics(layer);
if (poly instanceof Poly)
{
EGraphics overGra = ((Poly)poly).getGraphicsOverride();
if (overGra != null) gra = overGra;
}
}
Color col = null;
boolean opaque = true;
String patternName = null;
if (gra != null)
{
col = gra.getColor();
opaque = gra.getTransparentLayer() == 0;
if (gra.isPatternedOnDisplay()) patternName = layer.getName();
}
// use solid color if solid pattern or no pattern
if (patternName != null && !real)
{
int [] pattern = gra.getPattern();
boolean sol = true;
for(int i=0; i<8; i++)
if (pattern[i] != 0xFFFF) { sol = false; break; }
if (sol) patternName = null; else
{
printWriter.println(" <pattern id=\"" + patternName + "\" x=\"0\" y=\"0\" width=\"16\" height=\"8\" patternUnits=\"userSpaceOnUse\">");
for(int i=0; i<8; i++)
{
for (int k=0; k<16; k++)
{
if (((pattern[i]>>k)&1) != 0)
{
printWriter.println(" <rect x=\"" + (15-k) + "\" y=\"" + i + "\" width=\"1\" height=\"1\" fill=\"" + getColorDescription(col) + "\"/>");
}
}
}
printWriter.println(" </pattern>");
}
}
// 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;
}
}
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 (!real) return;
if (polyBox.getHeight() == 0)
{
svgDot(new Point2D.Double(polyBox.getCenterX(), polyBox.getCenterY()), col, opaque, patternName);
} else
{
svgLine(new Point2D.Double(polyBox.getCenterX(), polyBox.getMinY()),
new Point2D.Double(polyBox.getCenterX(), polyBox.getMaxY()), col, 0);
}
return;
} else if (polyBox.getHeight() == 0)
{
if (real) svgLine(new Point2D.Double(polyBox.getMinX(), polyBox.getCenterY()),
new Point2D.Double(polyBox.getMaxX(), polyBox.getCenterY()), col, 0);
return;
}
svgBox(polyBox, col, opaque, patternName);
return;
}
if (points.length == 1)
{
if (real) svgDot(points[0], col, opaque, patternName);
return;
}
if (points.length == 2)
{
if (real) svgLine(points[0], points[1], col, 0);
return;
}
svgPolygon(poly, col, opaque, patternName);
return;
}
if (!real) return;
if (type == Poly.Type.CLOSED)
{
Point2D lastPt = points[points.length-1];
for (int k = 0; k < points.length; k++)
{
svgLine(lastPt, points[k], col, 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++)
svgLine(points[k-1], points[k], col, lineType);
return;
}
if (type == Poly.Type.VECTORS)
{
for(int k=0; k<points.length; k += 2)
svgLine(points[k], points[k+1], col, 0);
return;
}
if (type == Poly.Type.CROSS || type == Poly.Type.BIGCROSS)
{
double x = poly.getCenterX();
double y = poly.getCenterY();
drawCross(x, y, col, type == Poly.Type.BIGCROSS);
return;
}
if (type == Poly.Type.CROSSED)
{
Rectangle2D bounds = poly.getBounds2D();
svgLine(new Point2D.Double(bounds.getMinX(), bounds.getMinY()), new Point2D.Double(bounds.getMinX(), bounds.getMaxY()), col, 0);
svgLine(new Point2D.Double(bounds.getMinX(), bounds.getMaxY()), new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()), col, 0);
svgLine(new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()), new Point2D.Double(bounds.getMaxX(), bounds.getMinY()), col, 0);
svgLine(new Point2D.Double(bounds.getMaxX(), bounds.getMinY()), new Point2D.Double(bounds.getMinX(), bounds.getMinY()), col, 0);
svgLine(new Point2D.Double(bounds.getMinX(), bounds.getMinY()), new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()), col, 0);
svgLine(new Point2D.Double(bounds.getMinX(), bounds.getMaxY()), new Point2D.Double(bounds.getMaxX(), bounds.getMinY()), col, 0);
return;
}
if (type == Poly.Type.DISC)
{
svgDisc(points[0], points[1], col, opaque, patternName);
return;
}
if (type == Poly.Type.CIRCLE || type == Poly.Type.THICKCIRCLE)
{
svgCircle(points[0], points[1], col, opaque, patternName);
return;
}
if (type == Poly.Type.CIRCLEARC || type == Poly.Type.THICKCIRCLEARC)
{
svgArc(points[0], points[1], points[2], col);
return;
}
// text
svgText((Poly)poly, col== null ? Color.black : col);
}
/**
* 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, Color col, boolean bigCross)
{
double amount = 0.25;
if (bigCross) amount = 0.5;
svgLine(new Point2D.Double(x-amount, y), new Point2D.Double(x+amount, y), col, 0);
svgLine(new Point2D.Double(x, y+amount), new Point2D.Double(x, y-amount), col, 0);
}
/**
* Method to draw a dot
* @param pt the center of the dot.
*/
private void svgDot(Point2D pt, Color col, boolean opaque, String patternName)
{
Point2D ps = svgXform(pt);
String style = getStyleDescription(col, opaque, patternName);
printWriter.println("<rect x=\"" + TextUtils.formatDouble(ps.getX()) + "\" y=\"" + TextUtils.formatDouble(ps.getY()) +
"\" width=\"1\" height=\"1\" " + style + "/>");
}
/**
* 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 svgLine(Point2D from, Point2D to, Color col, int pattern)
{
Point2D pt1 = svgXform(from);
Point2D pt2 = svgXform(to);
printWriter.print("<line x1=\"" + TextUtils.formatDouble(pt1.getX()) + "\" y1=\"" + TextUtils.formatDouble(pt1.getY()) +
"\" x2=\"" + TextUtils.formatDouble(pt2.getX()) + "\" y2=\"" + TextUtils.formatDouble(pt2.getY()) + "\"");
switch (pattern)
{
case 0:
printWriter.print(" style=\"stroke:" + getColorDescription(col) + "\"");
break;
case 1: // OPENEDT1 (dotted)
printWriter.print(" style=\"stroke-dasharray:1,5;stroke:" + getColorDescription(col) + "\"");
break;
case 2: // OPENEDT2 (dashed)
printWriter.print(" style=\"stroke-dasharray:8,4;stroke:" + getColorDescription(col) + "\"");
break;
case 3: // OPENEDT3 (thick)
printWriter.print(" style=\"stroke-width:3;stroke:" + getColorDescription(col) + "\"");
break;
}
printWriter.println("/>");
}
/**
* 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 svgArc(Point2D center, Point2D pt1, Point2D pt2, Color col)
{
// Emit: A<sx,sy> <r1,r2> <a> <l>,<p> <ex,ey>
// <sx,sy> is starting point of arc
// <r1,r2> are the two radii
// <a> is angle of starting point from center
// <l> is 1 if arc is more than 180 degrees, 0 otherwise
// <p> is 1 if arc goes in positive angle, 0 otherwise
// <ex,ey> is ending point of arc
Point2D pc = svgXform(center);
Point2D ps1 = svgXform(pt1);
Point2D ps2 = svgXform(pt2);
double radius = pc.distance(ps1);
double startAngle = ((3600 - DBMath.figureAngle(pc, ps2)) % 3600) / 10;
double endAngle = ((3600 - DBMath.figureAngle(pc, ps1)) % 3600) / 10;
double angleDiff = endAngle - startAngle;
if (angleDiff < 0) angleDiff += 360;
int largeAngle = angleDiff >= 180 ? 1 : 0;
int positive = 1;
printWriter.println("<path d=\"M " + TextUtils.formatDouble(ps1.getX()) + "," + TextUtils.formatDouble(ps1.getY()) +
" A" + radius + "," + radius + " " + startAngle + " " + largeAngle + "," + positive + " " +
TextUtils.formatDouble(ps2.getX()) + "," + TextUtils.formatDouble(ps2.getY()) + "\" fill=\"none\" stroke=\"" +
getColorDescription(col) + "\" />");
}
/**
* Method to draw an unfilled circle.
* @param center the center of the circle.
* @param pt a point on the circle.
*/
private void svgCircle(Point2D center, Point2D pt, Color col, boolean opaque, String patternName)
{
Point2D pc = svgXform(center);
Point2D ps = svgXform(pt);
double radius = pc.distance(ps);
String style = "style=\"stroke:" + getColorDescription(col) + (opaque ? "" : ";opacity:0.5") + ";fill:none\"";
printWriter.println("<circle cx=\"" + TextUtils.formatDouble(pc.getX()) + "\" cy=\"" + TextUtils.formatDouble(pc.getY()) +
"\" r=\"" + radius + "\" " + style + "/>");
}
/**
* Method to draw a filled circle.
* @param center the center of the circle.
* @param pt a point on the circle.
*/
private void svgDisc(Point2D center, Point2D pt, Color col, boolean opaque, String patternName)
{
Point2D pc = svgXform(center);
Point2D ps = svgXform(pt);
double radius = pc.distance(ps);
String style = getStyleDescription(col, opaque, patternName);
printWriter.println("<circle cx=\"" + TextUtils.formatDouble(pc.getX()) + "\" cy=\"" + TextUtils.formatDouble(pc.getY()) +
"\" r=\"" + radius + "\" " + style + "/>");
}
/**
* Method to draw a rectangle
* @param polyBox the rectangle to draw.
*/
private void svgBox(Rectangle2D polyBox, Color col, boolean opaque, String patternName)
{
Point2D pLow = svgXform(new Point2D.Double(polyBox.getMinX(), polyBox.getMaxY()));
Point2D pHigh = svgXform(new Point2D.Double(polyBox.getMaxX(), polyBox.getMinY()));
double lX = Math.min(pLow.getX(), pHigh.getX());
double hX = Math.max(pLow.getX(), pHigh.getX());
double lY = Math.min(pLow.getY(), pHigh.getY());
double hY = Math.max(pLow.getY(), pHigh.getY());
String style = getStyleDescription(col, opaque, patternName);
printWriter.println("<rect x=\"" + TextUtils.formatDouble(lX) + "\" y=\"" + TextUtils.formatDouble(lY) +
"\" width=\"" + TextUtils.formatDouble(hX-lX) + "\" height=\"" + TextUtils.formatDouble(hY-lY) + "\" " + style + "/>");
}
/**
* Method to draw an irregular polygon.
* @param poly the polygon to draw.
*/
private void svgPolygon(PolyBase poly, Color col, boolean opaque, String patternName)
{
Point2D [] points = poly.getPoints();
if (points.length == 0) return;
printWriter.print("<polygon points=\"");
for(int i=0; i<points.length; i++)
{
if (i != 0) printWriter.print(" ");
Point2D ps = svgXform(points[i]);
printWriter.print(TextUtils.formatDouble(ps.getX()) + "," + TextUtils.formatDouble(ps.getY()));
}
printWriter.println("\" " + getStyleDescription(col, opaque, patternName) + "/>");
}
private String getStyleDescription(Color col, boolean opaque, String patternName)
{
if (patternName != null) return "fill=\"url(#" + patternName + ")\"";
if (col == null) return "";
String style = "style=\"fill:" + getColorDescription(col) + (opaque ? "" : ";opacity:0.5") + "\"";
return style;
}
private String getColorDescription(Color col)
{
if (col == null) return "rgb(0,0,0)";
String style = "rgb(" + col.getRed() + "," + col.getGreen() + "," + col.getBlue() + ")";
return style;
}
/**
* Method to draw text.
* @param poly the text polygon to draw.
*/
private void svgText(Poly poly, Color col)
{
Poly.Type style = poly.getStyle();
TextDescriptor td = poly.getTextDescriptor();
if (td == null) return;
int size = (int)td.getTrueSize(localPrefs.wnd);
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;
// TODO: finish boxed text
if (poly.getStyle() == Poly.Type.TEXTBOX)
{
// treat like centered text
style = Poly.Type.TEXTCENT;
}
Point2D p;
String styleMsg = null;
double x, y;
if (style == Poly.Type.TEXTCENT)
{
p = svgXform(new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()));
x = p.getX(); y = p.getY()+size/2;
styleMsg = "text-anchor:middle";
} else if (style == Poly.Type.TEXTTOP)
{
p = svgXform(new Point2D.Double(bounds.getCenterX(), bounds.getMinY()));
x = p.getX(); y = p.getY()+size;
styleMsg = "text-anchor:middle";
} else if (style == Poly.Type.TEXTBOT)
{
p = svgXform(new Point2D.Double(bounds.getCenterX(), bounds.getMaxY()));
x = p.getX(); y = p.getY();
styleMsg = "text-anchor:middle";
} else if (style == Poly.Type.TEXTLEFT)
{
p = svgXform(new Point2D.Double(bounds.getMinX(), bounds.getCenterY()));
x = p.getX(); y = p.getY()+size/2;
} else if (style == Poly.Type.TEXTRIGHT)
{
p = svgXform(new Point2D.Double(bounds.getMaxX(), bounds.getCenterY()));
x = p.getX(); y = p.getY()+size/2;
styleMsg = "text-anchor:end";
} else if (style == Poly.Type.TEXTTOPLEFT)
{
p = svgXform(new Point2D.Double(bounds.getMinX(), bounds.getMinY()));
x = p.getX(); y = p.getY()+size;
} else if (style == Poly.Type.TEXTTOPRIGHT)
{
p = svgXform(new Point2D.Double(bounds.getMaxX(), bounds.getMinY()));
x = p.getX(); y = p.getY()+size;
styleMsg = "text-anchor:end";
} else if (style == Poly.Type.TEXTBOTLEFT)
{
p = svgXform(new Point2D.Double(bounds.getMinX(), bounds.getMaxY()));
x = p.getX(); y = p.getY();
} else if (style == Poly.Type.TEXTBOTRIGHT)
{
p = svgXform(new Point2D.Double(bounds.getMaxX(), bounds.getMaxY()));
x = p.getX(); y = p.getY();
styleMsg = "text-anchor:end";
} else return;
if (td.isBold()) styleMsg += "; font-weight: bold";
if (td.isItalic()) styleMsg += "; font-style: italic";
printWriter.print("<text x=\"" + TextUtils.formatDouble(x) + "\" y=\"" + TextUtils.formatDouble(y) +
"\" fill=\"" + getColorDescription(col) + "\" font-size=\"" + size + "\"");
if (td.isUnderline()) printWriter.print(" text-decoration=\"underline\"");
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(" font-family=\"" + fixedFaceName + "\"");
}
if (styleMsg != null) printWriter.print(" style=\"" + styleMsg + "\"");
TextDescriptor.Rotation rot = td.getRotation();
if (rot != TextDescriptor.Rotation.ROT0)
{
int amt = 270;
if (rot == TextDescriptor.Rotation.ROT180) amt = 180; else
if (rot == TextDescriptor.Rotation.ROT270) amt = 90;
printWriter.println(" transform=\"rotate(" + amt + " " + x + "," + y + ")\"");
}
printWriter.println(">");
printWriter.println(" " + text);
printWriter.println("</text>");
}
/****************************** SUPPORT ******************************/
/**
* Method to convert coordinates for display.
* @param pt the Electric coordinates.
* @return the SVG coordinates.
*/
private Point2D svgXform(Point2D pt)
{
Point2D result = new Point2D.Double();
matrix.transform(pt, result);
return result;
}
}