| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.xmlgraphics.ps; |
| |
| import java.awt.Color; |
| import java.awt.color.ColorSpace; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.text.DateFormat; |
| import java.text.DecimalFormat; |
| import java.text.DecimalFormatSymbols; |
| import java.util.Date; |
| import java.util.Locale; |
| import java.util.Stack; |
| |
| import javax.xml.transform.Source; |
| |
| import org.apache.xmlgraphics.ps.dsc.ResourceTracker; |
| |
| /** |
| * This class is used to output PostScript code to an OutputStream. |
| * |
| * @version $Id$ |
| */ |
| public class PSGenerator { |
| |
| /** |
| * Default postscript language level |
| */ |
| public static final int DEFAULT_LANGUAGE_LEVEL = 3; |
| |
| /** |
| * Indicator for the PostScript interpreter that the value is provided |
| * later in the document (mostly in the %%Trailer section). |
| * @deprecated Please use DSCConstants.ATEND. This constant was in the wrong place. |
| */ |
| public static final Object ATEND = DSCConstants.ATEND; |
| |
| /** Line feed used by PostScript */ |
| public static final char LF = '\n'; |
| |
| private OutputStream out; |
| private int psLevel = DEFAULT_LANGUAGE_LEVEL; |
| private boolean commentsEnabled = true; |
| |
| private Stack graphicsStateStack = new Stack(); |
| private PSState currentState; |
| //private DecimalFormat df3 = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.US)); |
| private DecimalFormat df3 = new DecimalFormat("0.###", new DecimalFormatSymbols(Locale.US)); |
| private DecimalFormat df5 = new DecimalFormat("0.#####", new DecimalFormatSymbols(Locale.US)); |
| |
| private StringBuffer tempBuffer = new StringBuffer(256); |
| |
| /** |
| * Creates a new instance. |
| * @param out the OutputStream to write the generated PostScript code to |
| */ |
| public PSGenerator(OutputStream out) { |
| this.out = out; |
| this.currentState = new PSState(); |
| //this.graphicsStateStack.push(this.currentState); |
| } |
| |
| /** |
| * Returns the OutputStream the PSGenerator writes to. |
| * @return the OutputStream |
| */ |
| public OutputStream getOutputStream() { |
| return this.out; |
| } |
| |
| /** |
| * Returns the selected PostScript level. |
| * @return the PostScript level |
| */ |
| public int getPSLevel() { |
| return this.psLevel; |
| } |
| |
| /** |
| * Sets the PostScript level that is used to generate the current document. |
| * @param level the PostScript level (currently 1, 2 and 3 are known) |
| */ |
| public void setPSLevel(int level) { |
| this.psLevel = level; |
| } |
| |
| /** |
| * Attempts to resolve the given URI. PSGenerator should be subclasses to provide more |
| * sophisticated URI resolution. |
| * @param uri URI to access |
| * @return A {@link javax.xml.transform.Source} object, or null if the URI |
| * cannot be resolved. |
| */ |
| public Source resolveURI(String uri) { |
| return new javax.xml.transform.stream.StreamSource(uri); |
| } |
| |
| /** |
| * Writes a newline character to the OutputStream. |
| * |
| * @throws IOException In case of an I/O problem |
| */ |
| public final void newLine() throws IOException { |
| out.write(LF); |
| } |
| |
| /** |
| * Formats a double value for PostScript output. |
| * |
| * @param value value to format |
| * @return the formatted value |
| */ |
| public String formatDouble(double value) { |
| return df3.format(value); |
| } |
| |
| /** |
| * Formats a double value for PostScript output (higher resolution). |
| * |
| * @param value value to format |
| * @return the formatted value |
| */ |
| public String formatDouble5(double value) { |
| return df5.format(value); |
| } |
| |
| /** |
| * Writes a PostScript command to the stream. |
| * |
| * @param cmd The PostScript code to be written. |
| * @exception IOException In case of an I/O problem |
| */ |
| public void write(String cmd) throws IOException { |
| /* @todo Check disabled until clarification. |
| if (cmd.length() > 255) { |
| throw new RuntimeException("PostScript command exceeded limit of 255 characters"); |
| } */ |
| out.write(cmd.getBytes("US-ASCII")); |
| } |
| |
| /** |
| * Writes a PostScript command to the stream and ends the line. |
| * |
| * @param cmd The PostScript code to be written. |
| * @exception IOException In case of an I/O problem |
| */ |
| public void writeln(String cmd) throws IOException { |
| write(cmd); |
| newLine(); |
| } |
| |
| /** |
| * Writes a comment to the stream and ends the line. Output of comments can |
| * be disabled to reduce the size of the generated file. |
| * |
| * @param comment comment to write |
| * @exception IOException In case of an I/O problem |
| */ |
| public void commentln(String comment) throws IOException { |
| if (this.commentsEnabled) { |
| writeln(comment); |
| } |
| } |
| |
| /** |
| * Writes encoded data to the PostScript stream. |
| * |
| * @param cmd The encoded PostScript code to be written. |
| * @exception IOException In case of an I/O problem |
| */ |
| public void writeByteArr(byte[] cmd) throws IOException { |
| out.write(cmd); |
| newLine(); |
| } |
| |
| |
| /** |
| * Flushes the OutputStream. |
| * |
| * @exception IOException In case of an I/O problem |
| */ |
| public void flush() throws IOException { |
| out.flush(); |
| } |
| |
| /** |
| * Escapes a character conforming to the rules established in the PostScript |
| * Language Reference (Search for "Literal Text Strings"). |
| * @param c character to escape |
| * @param target target StringBuffer to write the escaped character to |
| */ |
| public static final void escapeChar(char c, StringBuffer target) { |
| switch (c) { |
| case '\n': |
| target.append("\\n"); |
| break; |
| case '\r': |
| target.append("\\r"); |
| break; |
| case '\t': |
| target.append("\\t"); |
| break; |
| case '\b': |
| target.append("\\b"); |
| break; |
| case '\f': |
| target.append("\\f"); |
| break; |
| case '\\': |
| target.append("\\\\"); |
| break; |
| case '(': |
| target.append("\\("); |
| break; |
| case ')': |
| target.append("\\)"); |
| break; |
| default: |
| if (c > 255) { |
| //Ignoring non Latin-1 characters |
| target.append('?'); |
| } else if (c < 32 || c > 127) { |
| target.append('\\'); |
| |
| target.append((char)('0' + (c >> 6))); |
| target.append((char)('0' + ((c >> 3) % 8))); |
| target.append((char)('0' + (c % 8))); |
| //Integer.toOctalString(i) |
| } else { |
| target.append(c); |
| } |
| } |
| } |
| |
| |
| /** |
| * Converts text by applying escaping rules established in the DSC specs. |
| * @param text Text to convert |
| * @return String The resulting String |
| */ |
| public static final String convertStringToDSC(String text) { |
| return convertStringToDSC(text, false); |
| } |
| |
| /** |
| * Converts a <real> value for use in DSC comments. |
| * @param value the value to convert |
| * @return String The resulting String |
| */ |
| public static final String convertRealToDSC(float value) { |
| return Float.toString(value); |
| } |
| |
| /** |
| * Converts text by applying escaping rules established in the DSC specs. |
| * @param text Text to convert |
| * @param forceParentheses Force the use of parentheses |
| * @return String The resulting String |
| */ |
| public static final String convertStringToDSC(String text, |
| boolean forceParentheses) { |
| if ((text == null) || (text.length() == 0)) { |
| return "()"; |
| } else { |
| int initialSize = text.length(); |
| initialSize += initialSize / 2; |
| StringBuffer sb = new StringBuffer(initialSize); |
| if ((text.indexOf(' ') >= 0) || forceParentheses) { |
| sb.append('('); |
| for (int i = 0; i < text.length(); i++) { |
| final char c = text.charAt(i); |
| escapeChar(c, sb); |
| } |
| sb.append(')'); |
| return sb.toString(); |
| } else { |
| for (int i = 0; i < text.length(); i++) { |
| final char c = text.charAt(i); |
| escapeChar(c, sb); |
| } |
| return sb.toString(); |
| } |
| } |
| } |
| |
| |
| /** |
| * Writes a DSC comment to the output stream. |
| * @param name Name of the DSC comment |
| * @exception IOException In case of an I/O problem |
| * @see org.apache.xmlgraphics.ps.DSCConstants |
| */ |
| public void writeDSCComment(String name) throws IOException { |
| writeln("%%" + name); |
| } |
| |
| |
| /** |
| * Writes a DSC comment to the output stream. The parameter to the DSC |
| * comment can be any object. The object is converted to a String as |
| * necessary. |
| * @param name Name of the DSC comment |
| * @param param Single parameter to the DSC comment |
| * @exception IOException In case of an I/O problem |
| * @see org.apache.xmlgraphics.ps.DSCConstants |
| */ |
| public void writeDSCComment(String name, Object param) throws IOException { |
| writeDSCComment(name, new Object[] {param}); |
| } |
| |
| |
| /** |
| * Writes a DSC comment to the output stream. The parameters to the DSC |
| * comment can be any object. The objects are converted to Strings as |
| * necessary. Please see the source code to find out what parameters are |
| * currently supported. |
| * @param name Name of the DSC comment |
| * @param params Array of parameters to the DSC comment |
| * @exception IOException In case of an I/O problem |
| * @see org.apache.xmlgraphics.ps.DSCConstants |
| */ |
| public void writeDSCComment(String name, Object[] params) throws IOException { |
| tempBuffer.setLength(0); |
| tempBuffer.append("%%"); |
| tempBuffer.append(name); |
| if ((params != null) && (params.length > 0)) { |
| tempBuffer.append(": "); |
| for (int i = 0; i < params.length; i++) { |
| if (i > 0) { |
| tempBuffer.append(" "); |
| } |
| |
| if (params[i] instanceof String) { |
| tempBuffer.append(convertStringToDSC((String)params[i])); |
| } else if (params[i] == DSCConstants.ATEND) { |
| tempBuffer.append(DSCConstants.ATEND); |
| } else if (params[i] instanceof Double) { |
| tempBuffer.append(df3.format(params[i])); |
| } else if (params[i] instanceof Number) { |
| tempBuffer.append(params[i].toString()); |
| } else if (params[i] instanceof Date) { |
| DateFormat datef = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); |
| tempBuffer.append(convertStringToDSC(datef.format((Date)params[i]))); |
| } else if (params[i] instanceof PSResource) { |
| tempBuffer.append(((PSResource)params[i]).getResourceSpecification()); |
| } else { |
| throw new IllegalArgumentException("Unsupported parameter type: " |
| + params[i].getClass().getName()); |
| } |
| } |
| } |
| writeln(tempBuffer.toString()); |
| } |
| |
| |
| /** |
| * Saves the graphics state of the rendering engine. |
| * @exception IOException In case of an I/O problem |
| */ |
| public void saveGraphicsState() throws IOException { |
| writeln("gsave"); |
| |
| PSState state = new PSState(this.currentState, false); |
| this.graphicsStateStack.push(this.currentState); |
| this.currentState = state; |
| } |
| |
| /** |
| * Restores the last graphics state of the rendering engine. |
| * @return true if the state was restored, false if there's a stack underflow. |
| * @exception IOException In case of an I/O problem |
| */ |
| public boolean restoreGraphicsState() throws IOException { |
| if (this.graphicsStateStack.size() > 0) { |
| writeln("grestore"); |
| this.currentState = (PSState)this.graphicsStateStack.pop(); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| |
| /** |
| * Returns the current graphics state. |
| * @return the current graphics state |
| */ |
| public PSState getCurrentState() { |
| return this.currentState; |
| } |
| |
| /** |
| * Concats the transformation matrix. |
| * @param a A part |
| * @param b B part |
| * @param c C part |
| * @param d D part |
| * @param e E part |
| * @param f F part |
| * @exception IOException In case of an I/O problem |
| */ |
| public void concatMatrix(double a, double b, |
| double c, double d, |
| double e, double f) throws IOException { |
| AffineTransform at = new AffineTransform(a, b, c, d, e, f); |
| concatMatrix(at); |
| |
| } |
| |
| /** |
| * Concats the transformations matrix. |
| * @param matrix Matrix to use |
| * @exception IOException In case of an I/O problem |
| */ |
| public void concatMatrix(double[] matrix) throws IOException { |
| concatMatrix(matrix[0], matrix[1], |
| matrix[2], matrix[3], |
| matrix[4], matrix[5]); |
| } |
| |
| /** |
| * Formats a transformation matrix. |
| * @param at the AffineTransform with the matrix |
| * @return the formatted transformation matrix (example: "[1 0 0 1 0 0]") |
| */ |
| public String formatMatrix(AffineTransform at) { |
| double[] matrix = new double[6]; |
| at.getMatrix(matrix); |
| return "[" + formatDouble5(matrix[0]) + " " |
| + formatDouble5(matrix[1]) + " " |
| + formatDouble5(matrix[2]) + " " |
| + formatDouble5(matrix[3]) + " " |
| + formatDouble5(matrix[4]) + " " |
| + formatDouble5(matrix[5]) + "]"; |
| } |
| |
| /** |
| * Concats the transformations matric. |
| * @param at the AffineTransform whose matrix to use |
| * @exception IOException In case of an I/O problem |
| */ |
| public void concatMatrix(AffineTransform at) throws IOException { |
| getCurrentState().concatMatrix(at); |
| writeln(formatMatrix(at) + " concat"); |
| } |
| |
| /** |
| * Formats a Rectangle2D to an array. |
| * @param rect the rectangle |
| * @return the formatted array |
| */ |
| public String formatRectangleToArray(Rectangle2D rect) { |
| return "[" + formatDouble(rect.getX()) + " " |
| + formatDouble(rect.getY()) + " " |
| + formatDouble(rect.getWidth()) + " " |
| + formatDouble(rect.getHeight()) + "]"; |
| } |
| |
| /** |
| * Adds a rectangle to the current path. |
| * @param x upper left corner |
| * @param y upper left corner |
| * @param w width |
| * @param h height |
| * @exception IOException In case of an I/O problem |
| */ |
| public void defineRect(double x, double y, double w, double h) |
| throws IOException { |
| writeln(formatDouble(x) |
| + " " + formatDouble(y) |
| + " " + formatDouble(w) |
| + " " + formatDouble(h) |
| + " re"); |
| } |
| |
| /** |
| * Establishes the specified line cap style. |
| * @param linecap the line cap style (0, 1 or 2) as defined by the setlinecap command. |
| * @exception IOException In case of an I/O problem |
| */ |
| public void useLineCap(int linecap) throws IOException { |
| if (getCurrentState().useLineCap(linecap)) { |
| writeln(linecap + " setlinecap"); |
| } |
| } |
| |
| /** |
| * Establishes the specified line width. |
| * @param width the line width as defined by the setlinewidth command. |
| * @exception IOException In case of an I/O problem |
| */ |
| public void useLineWidth(double width) throws IOException { |
| if (getCurrentState().useLineWidth(width)) { |
| writeln(formatDouble(width) + " setlinewidth"); |
| } |
| } |
| |
| /** |
| * Establishes the specified dash pattern. |
| * @param pattern the dash pattern as defined by the setdash command. |
| * @exception IOException In case of an I/O problem |
| */ |
| public void useDash(String pattern) throws IOException { |
| if (pattern == null) { |
| pattern = PSState.DEFAULT_DASH; |
| } |
| if (getCurrentState().useDash(pattern)) { |
| writeln(pattern + " setdash"); |
| } |
| } |
| |
| /** |
| * Establishes the specified color (RGB). |
| * @param col the color as defined by the setrgbcolor command. |
| * @exception IOException In case of an I/O problem |
| * @deprecated use useColor method instead |
| */ |
| public void useRGBColor(Color col) throws IOException { |
| useColor(col); |
| } |
| |
| /** |
| * Establishes the specified color. |
| * @param col the color. |
| * @exception IOException In case of an I/O problem |
| */ |
| public void useColor(Color col) throws IOException { |
| if (getCurrentState().useColor(col)) { |
| writeln(convertColorToPS(col)); |
| } |
| } |
| |
| private String convertColorToPS(Color col) { |
| StringBuffer p = new StringBuffer(); |
| float[] comps = col.getColorComponents(null); |
| |
| if (col.getColorSpace().getType() == ColorSpace.TYPE_RGB) { |
| // according to pdfspec 12.1 p.399 |
| // if the colors are the same then just use the g or G operator |
| boolean same = (comps[0] == comps[1] |
| && comps[0] == comps[2]); |
| // output RGB |
| if (same) { |
| p.append(formatDouble(comps[0])); |
| } else { |
| for (int i = 0; i < col.getColorSpace().getNumComponents(); i++) { |
| if (i > 0) { |
| p.append(" "); |
| } |
| p.append(formatDouble(comps[i])); |
| } |
| } |
| if (same) { |
| p.append(" setgray"); |
| } else { |
| p.append(" setrgbcolor"); |
| } |
| } else if (col.getColorSpace().getType() == ColorSpace.TYPE_CMYK) { |
| // colorspace is CMYK |
| for (int i = 0; i < col.getColorSpace().getNumComponents(); i++) { |
| if (i > 0) { |
| p.append(" "); |
| } |
| p.append(formatDouble(comps[i])); |
| } |
| p.append(" setcmykcolor"); |
| } else { |
| // means we're in DeviceGray or Unknown. |
| // assume we're in DeviceGray, because otherwise we're screwed. |
| p.append(formatDouble(comps[0])); |
| p.append(" setgray"); |
| } |
| return p.toString(); |
| } |
| |
| /** |
| * Establishes the specified font and size. |
| * @param name name of the font for the "F" command (see FOP Std Proc Set) |
| * @param size size of the font |
| * @exception IOException In case of an I/O problem |
| */ |
| public void useFont(String name, float size) throws IOException { |
| if (getCurrentState().useFont(name, size)) { |
| writeln(name + " " + formatDouble(size) + " F"); |
| } |
| } |
| |
| private ResourceTracker resTracker = new ResourceTracker(); |
| |
| /** |
| * Resturns the ResourceTracker instance associated with this PSGenerator. |
| * @return the ResourceTracker instance or null if none is assigned |
| */ |
| public ResourceTracker getResourceTracker() { |
| return this.resTracker; |
| } |
| |
| /** |
| * Sets the ResourceTracker instance to be associated with this PSGenerator. |
| * @param resTracker the ResourceTracker instance |
| */ |
| public void setResourceTracker(ResourceTracker resTracker) { |
| this.resTracker = resTracker; |
| } |
| |
| /** |
| * Notifies the generator that a new page has been started and that the page resource |
| * set can be cleared. |
| * @deprecated Use the notifyStartNewPage() on ResourceTracker instead. |
| */ |
| public void notifyStartNewPage() { |
| getResourceTracker().notifyStartNewPage(); |
| } |
| |
| /** |
| * Notifies the generator about the usage of a resource on the current page. |
| * @param res the resource being used |
| * @param needed true if this is a needed resource, false for a supplied resource |
| * @deprecated Use the notifyResourceUsageOnPage() on ResourceTracker instead |
| */ |
| public void notifyResourceUsage(PSResource res, boolean needed) { |
| getResourceTracker().notifyResourceUsageOnPage(res); |
| } |
| |
| /** |
| * Writes a DSC comment for the accumulated used resources, either at page level or |
| * at document level. |
| * @param pageLevel true if the DSC comment for the page level should be generated, |
| * false for the document level (in the trailer) |
| * @exception IOException In case of an I/O problem |
| * @deprecated Use the writeResources() on ResourceTracker instead. |
| */ |
| public void writeResources(boolean pageLevel) throws IOException { |
| getResourceTracker().writeResources(pageLevel, this); |
| } |
| |
| /** |
| * Indicates whether a particular resource is supplied, rather than needed. |
| * @param res the resource |
| * @return true if the resource is registered as being supplied. |
| * @deprecated Use the isResourceSupplied() on ResourceTracker instead. |
| */ |
| public boolean isResourceSupplied(PSResource res) { |
| return getResourceTracker().isResourceSupplied(res); |
| } |
| |
| } |