001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: PSGenerator.java 549118 2007-06-20 14:27:03Z jeremias $ */
019:
020: package org.apache.xmlgraphics.ps;
021:
022: import java.awt.Color;
023: import java.awt.color.ColorSpace;
024: import java.awt.geom.AffineTransform;
025: import java.io.OutputStream;
026: import java.io.IOException;
027: import java.text.DateFormat;
028: import java.text.DecimalFormat;
029: import java.text.DecimalFormatSymbols;
030: import java.util.Date;
031: import java.util.Locale;
032: import java.util.Stack;
033:
034: import javax.xml.transform.Source;
035:
036: import org.apache.xmlgraphics.ps.dsc.ResourceTracker;
037:
038: /**
039: * This class is used to output PostScript code to an OutputStream.
040: *
041: * @version $Id: PSGenerator.java 549118 2007-06-20 14:27:03Z jeremias $
042: */
043: public class PSGenerator {
044:
045: /**
046: * Default postscript language level
047: */
048: public static final int DEFAULT_LANGUAGE_LEVEL = 3;
049:
050: /**
051: * Indicator for the PostScript interpreter that the value is provided
052: * later in the document (mostly in the %%Trailer section).
053: * @deprecated Please use DSCConstants.ATEND. This constant was in the wrong place.
054: */
055: public static final Object ATEND = DSCConstants.ATEND;
056:
057: /** Line feed used by PostScript */
058: public static final char LF = '\n';
059:
060: private OutputStream out;
061: private int psLevel = DEFAULT_LANGUAGE_LEVEL;
062: private boolean commentsEnabled = true;
063:
064: private Stack graphicsStateStack = new Stack();
065: private PSState currentState;
066: //private DecimalFormat df3 = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.US));
067: private DecimalFormat df3 = new DecimalFormat("0.###",
068: new DecimalFormatSymbols(Locale.US));
069: private DecimalFormat df5 = new DecimalFormat("0.#####",
070: new DecimalFormatSymbols(Locale.US));
071:
072: private StringBuffer tempBuffer = new StringBuffer(256);
073:
074: /**
075: * Creates a new instance.
076: * @param out the OutputStream to write the generated PostScript code to
077: */
078: public PSGenerator(OutputStream out) {
079: this .out = out;
080: this .currentState = new PSState();
081: //this.graphicsStateStack.push(this.currentState);
082: }
083:
084: /**
085: * Returns the OutputStream the PSGenerator writes to.
086: * @return the OutputStream
087: */
088: public OutputStream getOutputStream() {
089: return this .out;
090: }
091:
092: /**
093: * Returns the selected PostScript level.
094: * @return the PostScript level
095: */
096: public int getPSLevel() {
097: return this .psLevel;
098: }
099:
100: /**
101: * Sets the PostScript level that is used to generate the current document.
102: * @param level the PostScript level (currently 1, 2 and 3 are known)
103: */
104: public void setPSLevel(int level) {
105: this .psLevel = level;
106: }
107:
108: /**
109: * Attempts to resolve the given URI. PSGenerator should be subclasses to provide more
110: * sophisticated URI resolution.
111: * @param uri URI to access
112: * @return A {@link javax.xml.transform.Source} object, or null if the URI
113: * cannot be resolved.
114: */
115: public Source resolveURI(String uri) {
116: return new javax.xml.transform.stream.StreamSource(uri);
117: }
118:
119: /**
120: * Writes a newline character to the OutputStream.
121: *
122: * @throws IOException In case of an I/O problem
123: */
124: public final void newLine() throws IOException {
125: out.write(LF);
126: }
127:
128: /**
129: * Formats a double value for PostScript output.
130: *
131: * @param value value to format
132: * @return the formatted value
133: */
134: public String formatDouble(double value) {
135: return df3.format(value);
136: }
137:
138: /**
139: * Formats a double value for PostScript output (higher resolution).
140: *
141: * @param value value to format
142: * @return the formatted value
143: */
144: public String formatDouble5(double value) {
145: return df5.format(value);
146: }
147:
148: /**
149: * Writes a PostScript command to the stream.
150: *
151: * @param cmd The PostScript code to be written.
152: * @exception IOException In case of an I/O problem
153: */
154: public void write(String cmd) throws IOException {
155: /* @todo Check disabled until clarification.
156: if (cmd.length() > 255) {
157: throw new RuntimeException("PostScript command exceeded limit of 255 characters");
158: } */
159: out.write(cmd.getBytes("US-ASCII"));
160: }
161:
162: /**
163: * Writes a PostScript command to the stream and ends the line.
164: *
165: * @param cmd The PostScript code to be written.
166: * @exception IOException In case of an I/O problem
167: */
168: public void writeln(String cmd) throws IOException {
169: write(cmd);
170: newLine();
171: }
172:
173: /**
174: * Writes a comment to the stream and ends the line. Output of comments can
175: * be disabled to reduce the size of the generated file.
176: *
177: * @param comment comment to write
178: * @exception IOException In case of an I/O problem
179: */
180: public void commentln(String comment) throws IOException {
181: if (this .commentsEnabled) {
182: writeln(comment);
183: }
184: }
185:
186: /**
187: * Writes encoded data to the PostScript stream.
188: *
189: * @param cmd The encoded PostScript code to be written.
190: * @exception IOException In case of an I/O problem
191: */
192: public void writeByteArr(byte[] cmd) throws IOException {
193: out.write(cmd);
194: newLine();
195: }
196:
197: /**
198: * Flushes the OutputStream.
199: *
200: * @exception IOException In case of an I/O problem
201: */
202: public void flush() throws IOException {
203: out.flush();
204: }
205:
206: /**
207: * Escapes a character conforming to the rules established in the PostScript
208: * Language Reference (Search for "Literal Text Strings").
209: * @param c character to escape
210: * @param target target StringBuffer to write the escaped character to
211: */
212: public static final void escapeChar(char c, StringBuffer target) {
213: switch (c) {
214: case '\n':
215: target.append("\\n");
216: break;
217: case '\r':
218: target.append("\\r");
219: break;
220: case '\t':
221: target.append("\\t");
222: break;
223: case '\b':
224: target.append("\\b");
225: break;
226: case '\f':
227: target.append("\\f");
228: break;
229: case '\\':
230: target.append("\\\\");
231: break;
232: case '(':
233: target.append("\\(");
234: break;
235: case ')':
236: target.append("\\)");
237: break;
238: default:
239: if (c > 255) {
240: //Ignoring non Latin-1 characters
241: target.append('?');
242: } else if (c < 32 || c > 127) {
243: target.append('\\');
244:
245: target.append((char) ('0' + (c >> 6)));
246: target.append((char) ('0' + ((c >> 3) % 8)));
247: target.append((char) ('0' + (c % 8)));
248: //Integer.toOctalString(i)
249: } else {
250: target.append(c);
251: }
252: }
253: }
254:
255: /**
256: * Converts text by applying escaping rules established in the DSC specs.
257: * @param text Text to convert
258: * @return String The resulting String
259: */
260: public static final String convertStringToDSC(String text) {
261: return convertStringToDSC(text, false);
262: }
263:
264: /**
265: * Converts a <real> value for use in DSC comments.
266: * @param value the value to convert
267: * @return String The resulting String
268: */
269: public static final String convertRealToDSC(float value) {
270: return Float.toString(value);
271: }
272:
273: /**
274: * Converts text by applying escaping rules established in the DSC specs.
275: * @param text Text to convert
276: * @param forceParentheses Force the use of parentheses
277: * @return String The resulting String
278: */
279: public static final String convertStringToDSC(String text,
280: boolean forceParentheses) {
281: if ((text == null) || (text.length() == 0)) {
282: return "()";
283: } else {
284: int initialSize = text.length();
285: initialSize += initialSize / 2;
286: StringBuffer sb = new StringBuffer(initialSize);
287: if ((text.indexOf(' ') >= 0) || forceParentheses) {
288: sb.append('(');
289: for (int i = 0; i < text.length(); i++) {
290: final char c = text.charAt(i);
291: escapeChar(c, sb);
292: }
293: sb.append(')');
294: return sb.toString();
295: } else {
296: for (int i = 0; i < text.length(); i++) {
297: final char c = text.charAt(i);
298: escapeChar(c, sb);
299: }
300: return sb.toString();
301: }
302: }
303: }
304:
305: /**
306: * Writes a DSC comment to the output stream.
307: * @param name Name of the DSC comment
308: * @exception IOException In case of an I/O problem
309: * @see org.apache.xmlgraphics.ps.DSCConstants
310: */
311: public void writeDSCComment(String name) throws IOException {
312: writeln("%%" + name);
313: }
314:
315: /**
316: * Writes a DSC comment to the output stream. The parameter to the DSC
317: * comment can be any object. The object is converted to a String as
318: * necessary.
319: * @param name Name of the DSC comment
320: * @param param Single parameter to the DSC comment
321: * @exception IOException In case of an I/O problem
322: * @see org.apache.xmlgraphics.ps.DSCConstants
323: */
324: public void writeDSCComment(String name, Object param)
325: throws IOException {
326: writeDSCComment(name, new Object[] { param });
327: }
328:
329: /**
330: * Writes a DSC comment to the output stream. The parameters to the DSC
331: * comment can be any object. The objects are converted to Strings as
332: * necessary. Please see the source code to find out what parameters are
333: * currently supported.
334: * @param name Name of the DSC comment
335: * @param params Array of parameters to the DSC comment
336: * @exception IOException In case of an I/O problem
337: * @see org.apache.xmlgraphics.ps.DSCConstants
338: */
339: public void writeDSCComment(String name, Object[] params)
340: throws IOException {
341: tempBuffer.setLength(0);
342: tempBuffer.append("%%");
343: tempBuffer.append(name);
344: if ((params != null) && (params.length > 0)) {
345: tempBuffer.append(": ");
346: for (int i = 0; i < params.length; i++) {
347: if (i > 0) {
348: tempBuffer.append(" ");
349: }
350:
351: if (params[i] instanceof String) {
352: tempBuffer
353: .append(convertStringToDSC((String) params[i]));
354: } else if (params[i] == DSCConstants.ATEND) {
355: tempBuffer.append(DSCConstants.ATEND);
356: } else if (params[i] instanceof Double) {
357: tempBuffer.append(df3.format(params[i]));
358: } else if (params[i] instanceof Number) {
359: tempBuffer.append(params[i].toString());
360: } else if (params[i] instanceof Date) {
361: DateFormat datef = new java.text.SimpleDateFormat(
362: "yyyy-MM-dd'T'HH:mm:ss");
363: tempBuffer.append(convertStringToDSC(datef
364: .format((Date) params[i])));
365: } else if (params[i] instanceof PSResource) {
366: tempBuffer.append(((PSResource) params[i])
367: .getResourceSpecification());
368: } else {
369: throw new IllegalArgumentException(
370: "Unsupported parameter type: "
371: + params[i].getClass().getName());
372: }
373: }
374: }
375: writeln(tempBuffer.toString());
376: }
377:
378: /**
379: * Saves the graphics state of the rendering engine.
380: * @exception IOException In case of an I/O problem
381: */
382: public void saveGraphicsState() throws IOException {
383: writeln("gsave");
384:
385: PSState state = new PSState(this .currentState, false);
386: this .graphicsStateStack.push(this .currentState);
387: this .currentState = state;
388: }
389:
390: /**
391: * Restores the last graphics state of the rendering engine.
392: * @return true if the state was restored, false if there's a stack underflow.
393: * @exception IOException In case of an I/O problem
394: */
395: public boolean restoreGraphicsState() throws IOException {
396: if (this .graphicsStateStack.size() > 0) {
397: writeln("grestore");
398: this .currentState = (PSState) this .graphicsStateStack.pop();
399: return true;
400: } else {
401: return false;
402: }
403: }
404:
405: /**
406: * Returns the current graphics state.
407: * @return the current graphics state
408: */
409: public PSState getCurrentState() {
410: return this .currentState;
411: }
412:
413: /**
414: * Concats the transformation matrix.
415: * @param a A part
416: * @param b B part
417: * @param c C part
418: * @param d D part
419: * @param e E part
420: * @param f F part
421: * @exception IOException In case of an I/O problem
422: */
423: public void concatMatrix(double a, double b, double c, double d,
424: double e, double f) throws IOException {
425: AffineTransform at = new AffineTransform(a, b, c, d, e, f);
426: concatMatrix(at);
427:
428: }
429:
430: /**
431: * Concats the transformations matrix.
432: * @param matrix Matrix to use
433: * @exception IOException In case of an I/O problem
434: */
435: public void concatMatrix(double[] matrix) throws IOException {
436: concatMatrix(matrix[0], matrix[1], matrix[2], matrix[3],
437: matrix[4], matrix[5]);
438: }
439:
440: /**
441: * Concats the transformations matric.
442: * @param at the AffineTransform whose matrix to use
443: * @exception IOException In case of an I/O problem
444: */
445: public void concatMatrix(AffineTransform at) throws IOException {
446: double[] matrix = new double[6];
447: at.getMatrix(matrix);
448: getCurrentState().concatMatrix(at);
449: writeln("[" + formatDouble5(matrix[0]) + " "
450: + formatDouble5(matrix[1]) + " "
451: + formatDouble5(matrix[2]) + " "
452: + formatDouble5(matrix[3]) + " "
453: + formatDouble5(matrix[4]) + " "
454: + formatDouble5(matrix[5]) + "] concat");
455: }
456:
457: /**
458: * Adds a rectangle to the current path.
459: * @param x upper left corner
460: * @param y upper left corner
461: * @param w width
462: * @param h height
463: * @exception IOException In case of an I/O problem
464: */
465: public void defineRect(double x, double y, double w, double h)
466: throws IOException {
467: writeln(formatDouble(x) + " " + formatDouble(y) + " "
468: + formatDouble(w) + " " + formatDouble(h) + " re");
469: }
470:
471: /**
472: * Establishes the specified line cap style.
473: * @param linecap the line cap style (0, 1 or 2) as defined by the setlinecap command.
474: * @exception IOException In case of an I/O problem
475: */
476: public void useLineCap(int linecap) throws IOException {
477: if (getCurrentState().useLineCap(linecap)) {
478: writeln(linecap + " setlinecap");
479: }
480: }
481:
482: /**
483: * Establishes the specified line width.
484: * @param width the line width as defined by the setlinewidth command.
485: * @exception IOException In case of an I/O problem
486: */
487: public void useLineWidth(double width) throws IOException {
488: if (getCurrentState().useLineWidth(width)) {
489: writeln(formatDouble(width) + " setlinewidth");
490: }
491: }
492:
493: /**
494: * Establishes the specified dash pattern.
495: * @param pattern the dash pattern as defined by the setdash command.
496: * @exception IOException In case of an I/O problem
497: */
498: public void useDash(String pattern) throws IOException {
499: if (pattern == null) {
500: pattern = PSState.DEFAULT_DASH;
501: }
502: if (getCurrentState().useDash(pattern)) {
503: writeln(pattern + " setdash");
504: }
505: }
506:
507: /**
508: * Establishes the specified color (RGB).
509: * @param col the color as defined by the setrgbcolor command.
510: * @exception IOException In case of an I/O problem
511: * @deprecated use useColor method instead
512: */
513: public void useRGBColor(Color col) throws IOException {
514: useColor(col);
515: }
516:
517: /**
518: * Establishes the specified color.
519: * @param col the color.
520: * @exception IOException In case of an I/O problem
521: */
522: public void useColor(Color col) throws IOException {
523: if (getCurrentState().useColor(col)) {
524: writeln(convertColorToPS(col));
525: }
526: }
527:
528: private String convertColorToPS(Color col) {
529: StringBuffer p = new StringBuffer();
530: float[] comps = col.getColorComponents(null);
531:
532: if (col.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
533: // according to pdfspec 12.1 p.399
534: // if the colors are the same then just use the g or G operator
535: boolean same = (comps[0] == comps[1] && comps[0] == comps[2]);
536: // output RGB
537: if (same) {
538: p.append(formatDouble(comps[0]));
539: } else {
540: for (int i = 0; i < col.getColorSpace()
541: .getNumComponents(); i++) {
542: if (i > 0) {
543: p.append(" ");
544: }
545: p.append(formatDouble(comps[i]));
546: }
547: }
548: if (same) {
549: p.append(" setgray");
550: } else {
551: p.append(" setrgbcolor");
552: }
553: } else if (col.getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
554: // colorspace is CMYK
555: for (int i = 0; i < col.getColorSpace().getNumComponents(); i++) {
556: if (i > 0) {
557: p.append(" ");
558: }
559: p.append(formatDouble(comps[i]));
560: }
561: p.append(" setcmykcolor");
562: } else {
563: // means we're in DeviceGray or Unknown.
564: // assume we're in DeviceGray, because otherwise we're screwed.
565: p.append(formatDouble(comps[0]));
566: p.append(" setgray");
567: }
568: return p.toString();
569: }
570:
571: /**
572: * Establishes the specified font and size.
573: * @param name name of the font for the "F" command (see FOP Std Proc Set)
574: * @param size size of the font
575: * @exception IOException In case of an I/O problem
576: */
577: public void useFont(String name, float size) throws IOException {
578: if (getCurrentState().useFont(name, size)) {
579: writeln(name + " " + formatDouble(size) + " F");
580: }
581: }
582:
583: private ResourceTracker resTracker = new ResourceTracker();
584:
585: /**
586: * Resturns the ResourceTracker instance associated with this PSGenerator.
587: * @return the ResourceTracker instance or null if none is assigned
588: */
589: public ResourceTracker getResourceTracker() {
590: return this .resTracker;
591: }
592:
593: /**
594: * Sets the ResourceTracker instance to be associated with this PSGenerator.
595: * @param resTracker the ResourceTracker instance
596: */
597: public void setResourceTracker(ResourceTracker resTracker) {
598: this .resTracker = resTracker;
599: }
600:
601: /**
602: * Notifies the generator that a new page has been started and that the page resource
603: * set can be cleared.
604: * @deprecated Use the notifyStartNewPage() on ResourceTracker instead.
605: */
606: public void notifyStartNewPage() {
607: getResourceTracker().notifyStartNewPage();
608: }
609:
610: /**
611: * Notifies the generator about the usage of a resource on the current page.
612: * @param res the resource being used
613: * @param needed true if this is a needed resource, false for a supplied resource
614: * @deprecated Use the notifyResourceUsageOnPage() on ResourceTracker instead
615: */
616: public void notifyResourceUsage(PSResource res, boolean needed) {
617: getResourceTracker().notifyResourceUsageOnPage(res);
618: }
619:
620: /**
621: * Writes a DSC comment for the accumulated used resources, either at page level or
622: * at document level.
623: * @param pageLevel true if the DSC comment for the page level should be generated,
624: * false for the document level (in the trailer)
625: * @exception IOException In case of an I/O problem
626: * @deprecated Use the writeResources() on ResourceTracker instead.
627: */
628: public void writeResources(boolean pageLevel) throws IOException {
629: getResourceTracker().writeResources(pageLevel, this );
630: }
631:
632: /**
633: * Indicates whether a particular resource is supplied, rather than needed.
634: * @param res the resource
635: * @return true if the resource is registered as being supplied.
636: * @deprecated Use the isResourceSupplied() on ResourceTracker instead.
637: */
638: public boolean isResourceSupplied(PSResource res) {
639: return getResourceTracker().isResourceSupplied(res);
640: }
641:
642: }
|