// Copyright 2000-2003 FreeHEP package org.freehep.graphicsio.ps; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import java.awt.image.*; import java.awt.image.renderable.*; import java.io.*; import java.text.*; import java.util.*; import org.freehep.graphics2d.GenericTagHandler; import org.freehep.graphics2d.TagString; import org.freehep.graphics2d.PrintColor; import org.freehep.graphicsio.ImageGraphics2D; import org.freehep.graphicsio.MultiPageDocument; import org.freehep.graphicsio.PageConstants; import org.freehep.graphicsio.FontConstants; import org.freehep.graphicsio.InfoConstants; import org.freehep.graphicsio.ImageConstants; import org.freehep.graphicsio.AbstractVectorGraphicsIO; import org.freehep.graphicsio.font.FontUtilities; import org.freehep.graphicsio.font.encoding.CharTable; import org.freehep.graphicsio.font.encoding.Lookup; import org.freehep.graphicsio.raw.RawImageWriteParam; import org.freehep.util.UserProperties; import org.freehep.util.io.ASCII85OutputStream; import org.freehep.util.io.FlateOutputStream; import org.freehep.util.images.ImageUtilities; /** * @author Charles Loomis * @author Mark Donszelmann * @version $Id: PSGraphics2D.java,v 1.20 2003/11/25 21:49:38 duns Exp $ */ public class PSGraphics2D extends AbstractVectorGraphicsIO implements MultiPageDocument, FontUtilities.ShowString { private static final String rootKey = PSGraphics2D.class.getName(); public static final String BACKGROUND = rootKey+"."+PageConstants.BACKGROUND; public static final String BACKGROUND_COLOR = rootKey+"."+PageConstants.BACKGROUND_COLOR; public static final String PAGE_SIZE = rootKey+"."+PageConstants.PAGE_SIZE; public static final String PAGE_MARGINS = rootKey+"."+PageConstants.PAGE_MARGINS; public static final String ORIENTATION = rootKey+"."+PageConstants.ORIENTATION; public static final String FIT_TO_PAGE = rootKey+"."+PageConstants.FIT_TO_PAGE; public static final String EMBED_FONTS = rootKey+"."+FontConstants.EMBED_FONTS; public static final String EMBED_FONTS_AS = rootKey+"."+FontConstants.EMBED_FONTS_AS; public static final String FOR = rootKey+"."+InfoConstants.FOR; public static final String TITLE = rootKey+"."+InfoConstants.TITLE; public static final String PREVIEW = rootKey+".Preview"; public static final String PREVIEW_BITS = rootKey+".PreviewBits"; public static final String WRITE_IMAGES_AS = rootKey+"."+ImageConstants.WRITE_IMAGES_AS; private static final UserProperties defaultProperties = new UserProperties(); static { defaultProperties.setProperty(BACKGROUND, false); defaultProperties.setProperty(BACKGROUND_COLOR, Color.GRAY); defaultProperties.setProperty(PAGE_SIZE, PageConstants.INTERNATIONAL); defaultProperties.setProperty(PAGE_MARGINS, PageConstants.getMargins(PageConstants.SMALL)); defaultProperties.setProperty(ORIENTATION, PageConstants.LANDSCAPE); defaultProperties.setProperty(FIT_TO_PAGE, true); defaultProperties.setProperty(EMBED_FONTS, false); defaultProperties.setProperty(EMBED_FONTS_AS, FontConstants.EMBED_FONTS_TYPE3); defaultProperties.setProperty(FOR, ""); defaultProperties.setProperty(TITLE, ""); defaultProperties.setProperty(PREVIEW, false); defaultProperties.setProperty(PREVIEW_BITS, 8); defaultProperties.setProperty(WRITE_IMAGES_AS, ImageConstants.SMALLEST); } public static Properties getDefaultProperties() { return defaultProperties; } public static final String version = "$Revision: 1.20 $"; public static final int LEVEL_2 = 2; public static final int LEVEL_3 = 3; private static final double FONTSIZE_CORRECTION = 1.0; /** * Default flag for allowing clip regions to be written */ private static boolean enableClip = true; // remember which fonts are used private PSFontTable fontTable; // The private writer used for this file. protected OutputStream ros; protected PrintStream os; // Remember if the font is already set. The font is not set when // setFont is called, but only when a string is actually drawn. private boolean fontSet = false; private boolean multiPage; private int currentPage; private int postscriptLevel = LEVEL_3; // Private array to do lookups of the symbols. private static String [] psSymbolNames = new String[NUMBER_OF_SYMBOLS]; private static Hashtable compositeFonts = new Hashtable(); static { for (int i=0; i1) { os.println((nPoints-1)+" "+xPoints[0]+" "+yPoints[0]+" OPL"); for (int i=1; i1) { os.println((nPoints-1)+" "+xPoints[0]+" "+yPoints[0]+" OPL"); for (int i=1; i1) { os.println((nPoints-1)+" "+xPoints[0]+" "+yPoints[0]+" CPL"); for (int i=1; i1) { os.println((nPoints-1)+" "+xPoints[0]+" "+yPoints[0]+" CPL"); for (int i=1; i1) { os.println((nPoints-1)+" "+xPoints[0]+" "+yPoints[0]+" FPL"); for (int i=1; i1) { os.println((nPoints-1)+" "+xPoints[0]+" "+yPoints[0]+" FPL"); for (int i=1; i=NUMBER_OF_SYMBOLS) ? "plus" : psSymbolNames[symbol]); } public void fillSymbol(double x, double y, double size, int symbol) { if (size<=0) return; os.print(fixedPrecision(x)+" "+ fixedPrecision(y)+" "+ fixedPrecision(size)+" "); if (symbol<0 || symbol>=NUMBER_OF_SYMBOLS) { os.println("plus"); return; } if (symbol >= SYMBOL_CIRCLE) { os.print("f"); } os.println(psSymbolNames[symbol]); } /* 5.1.4. shapes */ public void draw(Shape shape) { try { writePath(shape); os.println("S"); } catch (IOException e) { handleException(e); } } public void fill(Shape shape) { try { boolean eofill = writePath(shape); os.println(((eofill) ? "f*" : "f")); } catch (IOException e) { handleException(e); } } public void fillAndDraw(Shape shape, Color fillColor) { try { setPSColor(fillColor, true); boolean eofill = writePath(shape); os.println(((eofill) ? "B*" : "B")); } catch (IOException e) { handleException(e); } } /* 5.2 Images */ public void copyArea(int x, int y, int width, int height, int dx, int dy) { writeWarning(getClass()+": copyArea(int, int, int, int, int, int) not implemented."); } protected void writeImage(RenderedImage image, AffineTransform xform, Color bkg) throws IOException { // FIXME FREEHEP-374 if (bkg == null) bkg = getBackground(); image = ImageUtilities.createRenderedImage(image, bkg); // Write out the PostScript code to start an image // definition. int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); AffineTransform imageTransform = new AffineTransform( imageWidth, 0.0, 0.0, imageHeight, 0.0, 0.0); xform.concatenate(imageTransform); os.println("gsave /DeviceRGB setcolorspace"); // os.println(x+" "+y+" translate"); // os.println(width+" "+height+" scale"); transform(xform); os.println("<<"); os.println("/ImageType 1"); os.println("/Width "+imageWidth+ " /Height "+imageHeight); os.println("/BitsPerComponent 8"); os.println("/Decode [0 1 0 1 0 1]"); os.println("/ImageMatrix ["+imageWidth+ " 0 0 "+imageHeight+" 0 0]"); String writeAs = getProperty(WRITE_IMAGES_AS); byte[] flateBytes = null; if (writeAs.equals(ImageConstants.ZLIB) || writeAs.equals(ImageConstants.SMALLEST)) { ByteArrayOutputStream flate = new ByteArrayOutputStream(); ASCII85OutputStream flate85 = new ASCII85OutputStream(flate); FlateOutputStream fos = new FlateOutputStream(flate85); UserProperties props = new UserProperties(); props.setProperty(RawImageWriteParam.BACKGROUND, bkg); props.setProperty(RawImageWriteParam.CODE, "RGB"); props.setProperty(RawImageWriteParam.PAD, 1); ImageGraphics2D.writeImage(image, "raw", props, fos); fos.close(); flateBytes = flate.toByteArray(); } byte[] jpgBytes = null; if (writeAs.equals(ImageConstants.JPG) || writeAs.equals(ImageConstants.SMALLEST)) { ByteArrayOutputStream jpg = new ByteArrayOutputStream(); ASCII85OutputStream jpg85 = new ASCII85OutputStream(jpg); ImageGraphics2D.writeImage(image, "jpg", new Properties(), jpg85); jpg85.close(); jpgBytes = jpg.toByteArray(); } String encode; byte[] imageBytes; if (writeAs.equals(ImageConstants.ZLIB)) { encode = "Flate"; imageBytes = flateBytes; } else if (writeAs.equals(ImageConstants.JPG)) { encode = "DCT"; imageBytes = jpgBytes; } else { encode = (jpgBytes.length < 0.5*flateBytes.length) ? "DCT" : "Flate"; imageBytes = encode.equals("DCT") ? jpgBytes : flateBytes; } os.println("/DataSource currentfile "+ "/ASCII85Decode filter " + "/"+encode+"Decode filter " ); os.println(">> image"); os.write(imageBytes); os.println(""); os.println("grestore"); } /* 5.3. Strings */ protected void writeString(String str, double x, double y) throws IOException { showCharacterCodes(str, x, y); // writeFont(); // os.println("("+escapeString(str)+") "+ // fixedPrecision(x)+" "+ // fixedPrecision(y)+" STR"); } // FIXME compare this with AVGIO public void drawString(String str, double x, double y, int horizontal, int vertical, boolean framed, Color frameColor, double frameWidth, boolean banner, Color bannerColor) { try { LineMetrics metrics = getFont().getLineMetrics(str, getFontRenderContext()); double width = getFont().getStringBounds(str, getFontRenderContext()).getWidth(); double height = metrics.getHeight(); double descent = metrics.getDescent(); Rectangle2D textSize = new Rectangle2D.Double(0, descent-height, width, height); double adjustment = (getFont().getSize2D()*2)/10; Point2D textUL = drawFrameAndBanner(x, y, textSize, adjustment, framed, frameColor, frameWidth, banner, bannerColor, horizontal, vertical); drawString(str, textUL.getX(), textUL.getY()); } catch (IOException e) { handleException(e); } } // FIXME compare this with AVGIO public void drawString(TagString str, double x, double y, int horizontal, int vertical, boolean framed, Color frameColor, double frameWidth, boolean banner, Color bannerColor) { try { GenericTagHandler tagHandler = new GenericTagHandler(this); Rectangle2D r = tagHandler.stringSize(str); double adjustment = (getFont().getSize2D()*2)/10; Point2D textUL = drawFrameAndBanner(x, y, r, adjustment, framed, frameColor, frameWidth, banner, bannerColor, horizontal, vertical); tagHandler.print(str, textUL.getX(), textUL.getY()); } catch (IOException e) { handleException(e); } } public void drawString(AttributedCharacterIterator iterator, float x, float y) { writeWarning(getClass()+": drawString(AttributedCharacterIterator, float, float) not implemented."); } public void drawGlyphVector(GlyphVector g, float x, float y) { writeWarning(getClass()+": drawGlyphVector(GlyphVector, float, float) not implemented."); } /*================================================================================ | 6. Transformations *================================================================================*/ public void setTransform(AffineTransform tx) { super.setTransform(tx); if (tx == null) tx = new AffineTransform(); os.println("[ "+ fixedPrecision(tx.getScaleX())+" "+ fixedPrecision(tx.getShearY())+" "+ fixedPrecision(tx.getShearX())+" "+ fixedPrecision(tx.getScaleY())+" "+ fixedPrecision(tx.getTranslateX())+" "+ fixedPrecision(tx.getTranslateY())+ " ] defaultmatrix matrix concatmatrix setmatrix"); } public void transform(AffineTransform transform) { super.transform(transform); os.println("[ "+ fixedPrecision(transform.getScaleX())+" "+ fixedPrecision(transform.getShearY())+" "+ fixedPrecision(transform.getShearX())+" "+ fixedPrecision(transform.getScaleY())+" "+ fixedPrecision(transform.getTranslateX())+" "+ fixedPrecision(transform.getTranslateY())+" ] concat"); } public void translate(double x, double y) { super.translate(x, y); os.println(fixedPrecision(x)+" "+ fixedPrecision(y)+" translate"); } public void rotate(double theta) { super.rotate(theta); os.println(fixedPrecision(Math.toDegrees(theta))+" rotate"); } public void scale(double sx, double sy) { super.scale(sx, sy); os.println(fixedPrecision(sx)+" "+fixedPrecision(sy)+" scale"); } public void shear(double shx, double shy) { super.shear(shx, shy); os.println("[ 1.0 "+fixedPrecision(shy)+" "+fixedPrecision(shx)+" 1.0 0.0 0.0 ] concat"); } protected void writeTransform(AffineTransform tx) throws IOException { // all written with higher level methods } /*================================================================================ | 7. Clipping *================================================================================*/ /** * Clips shape. PS only allows to intersect the currentClip so this calls clip(Shape). * * @param shape used for clipping */ public void setClip(Shape shape) { clip(shape); } protected void writeClip(Rectangle r) throws IOException { if (r == null) return; if (enableClip) { os.println(r.x+" "+r.y+" "+r.width+" "+r.height+" rc"); } } protected void writeClip(Rectangle2D r2d) throws IOException { if (r2d == null) return; if (enableClip) { os.println(fixedPrecision(r2d.getX())+" "+ fixedPrecision(r2d.getY())+" "+ fixedPrecision(r2d.getWidth())+" "+ fixedPrecision(r2d.getHeight())+" rc"); } } protected void writeClip(Shape s) throws IOException { if (s == null) return; boolean eofill = writePath(s); os.println(((eofill) ? "W*" : "W")); } /** * Write the path of the current shape to the output file. Return * a boolean indicating whether or not the even-odd rule for * filling should be used. */ private boolean writePath(Shape s) throws IOException { os.println("newpath"); PSPathConstructor path = new PSPathConstructor(os, true, false); return path.addPath(s); } /*================================================================================ | 8. Graphics State *================================================================================*/ /* 8.1. stroke/linewidth */ protected void writeWidth(float width) throws IOException { os.println(fixedPrecision(width)+" w"); } protected void writeCap(int cap) throws IOException { switch (cap) { default: case BasicStroke.CAP_BUTT: os.println("0 J"); break; case BasicStroke.CAP_ROUND: os.println("1 J"); break; case BasicStroke.CAP_SQUARE: os.println("2 J"); break; } } protected void writeJoin(int join) throws IOException { switch (join) { default: case BasicStroke.JOIN_MITER: os.println("0 j"); break; case BasicStroke.JOIN_ROUND: os.println("1 j"); break; case BasicStroke.JOIN_BEVEL: os.println("2 j"); break; } } protected void writeMiterLimit(float limit) throws IOException { os.println(fixedPrecision(limit)+" M"); } protected void writeDash(double[] dash, double phase) throws IOException { os.print("[ "); for (int i=0; i= LEVEL_3) { float[] rgb1 = paint.getColor1().getRGBColorComponents(null); float[] rgb2 = paint.getColor2().getRGBColorComponents(null); Point2D p1 = paint.getPoint1(); Point2D p2 = paint.getPoint2(); os.println("<< /PatternType 2"); os.println(" /Shading"); os.println(" << /ShadingType 2"); os.println(" /ColorSpace /DeviceRGB"); os.println(" /Coords ["+ p1.getX()+" "+p1.getY()+" "+ p2.getX()+" "+p2.getY()+"]"); os.println(" /Function"); os.println(" << /FunctionType 2"); os.println(" /Domain [0 1]"); os.println(" /Range [0 1 0 1 0 1]"); os.println(" /C0 ["+rgb1[0]+" "+rgb1[1]+" "+rgb1[2]+"]"); os.println(" /C1 ["+rgb2[0]+" "+rgb2[1]+" "+rgb2[2]+"]"); os.println(" /N 1"); os.println(" >>"); os.println(" /Extend [true true]"); os.println(" >>"); os.println(">>"); os.println("matrix makepattern setpattern"); } else { writeComment("Gradient fill not supported by ps level 2. "+ "Replacing with intermediate color."); setColor(PrintColor.mixColor(paint.getColor1(), paint.getColor2())); } } protected void writePaint(TexturePaint paint) throws IOException { BufferedImage img = paint.getImage(); os.println("<< /PatternType 1"); os.println(" /PaintType 1"); os.println(" /TilingType 1"); os.println(" /BBox [0 0 "+img.getWidth() + " " +img.getHeight()+"]"); os.println(" /XStep "+paint.getAnchorRect().getWidth()); os.println(" /YStep "+paint.getAnchorRect().getHeight()); os.println(" /PaintProc"); os.println(" {"); os.println(" begin"); os.println(" /DeviceRGB setcolorspace"); os.println(" 0 0 translate"); os.println(" "+img.getWidth()+" "+img.getHeight()+" scale"); os.println(" <<"); os.println(" /ImageType 1"); os.println(" /Width "+img.getWidth()); os.println(" /Height "+img.getWidth()); os.println(" /BitsPerComponent 8"); os.println(" /Decode [0 1 0 1 0 1]"); os.println(" /ImageMatrix ["+img.getWidth()+" 0 0 "+img.getHeight()+" 0 0]"); os.println(" /DataSource ( Z Z Z Z Z Z Z Z Z ZZ ZZ ZZ ZZ ZZ ZZ ZZ Z)"); os.println(" >> image"); os.println(" end"); os.println(" } bind"); os.println(">>"); os.println("matrix makepattern setpattern"); } protected void writePaint(Paint p) throws IOException { writeWarning(getClass()+": writePaint(Paint) not implemented for "+p.getClass()); } /* 8.3 fonts */ /** * This method sets the current font. However, it does not write it to the file. * When drawString() wants to show text, it has to call writeFont() first, * which will actually write the current font to the document. Thus we avoid * embedding unused fonts (e.g. the Dialog font which is set by java for * whatsoever reason */ public void setFont(Font font) { if (!font.equals(getFont())) { fontSet = false; } super.setFont(font); } /*================================================================================ | 9. Auxiliary *================================================================================*/ public GraphicsConfiguration getDeviceConfiguration() { writeWarning(getClass()+": getDeviceConfiguration() not implemented."); return null; } public boolean hit(Rectangle rect, Shape s, boolean onStroke) { writeWarning(getClass()+": hit(Rectangle, Shape, boolean) not implemented."); return false; } /** * Embed a PostScript comment into the output file. */ public void writeComment(String s) throws IOException { os.println("% "+s); } public String toString() { return "PSGraphics2D"; } /*================================================================================ | 10. Private/Utility *================================================================================*/ private String convertToUnicodeString(String str) { // Start with an empty buffer and copy the string contents // into it. StringBuffer codedString = new StringBuffer(); for (int i=0; i>>8; String cbyte = Integer.toOctalString(cvalue); String fbyte = Integer.toOctalString(fvalue); // Put in the font byte first. codedString.append('\\'); for (int j=0; j<(3-fbyte.length()); j++) { codedString.append('0'); } codedString.append(fbyte); // Now the character itself. Put in non-printable // characters, the backslash, the percent sign, and // parentheses as octal numbers instead. if (cvalue<32 || cvalue>126 || cvalue=='\\' || cvalue=='%' || cvalue=='(' || cvalue==')') { codedString.append('\\'); for (int j=0; j<(3-cbyte.length()); j++) { codedString.append('0'); } codedString.append(cbyte); } else { codedString.append((char) cvalue); } } return codedString.toString(); } private TagString escapeTagString(TagString str) { return str; } // private TagString escapeTagString(TagString str) { // // Must protect against unbalanced parentheses in the string, // // as well as, occurances of "%", and "\". // // Copy string to temporary string buffer. // StringBuffer temp = new StringBuffer(str.toString()); // // Loop over all characters in the string and escape the // // parentheses. // int i = 0; // while (i=0 && vertical=0 && horizontal