You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PSGenerator.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /*
  2. * Copyright 1999-2005 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id$ */
  17. package org.apache.fop.render.ps;
  18. import java.awt.Color;
  19. import java.awt.geom.AffineTransform;
  20. import java.io.OutputStream;
  21. import java.io.IOException;
  22. import java.text.DateFormat;
  23. import java.text.DecimalFormat;
  24. import java.text.DecimalFormatSymbols;
  25. import java.util.Date;
  26. import java.util.Iterator;
  27. import java.util.Locale;
  28. import java.util.Set;
  29. import java.util.Stack;
  30. import javax.xml.transform.Source;
  31. /**
  32. * This class is used to output PostScript code to an OutputStream.
  33. *
  34. * @author <a href="mailto:fop-dev@xmlgraphics.apache.org">Apache FOP Development Team</a>
  35. * @version $Id$
  36. */
  37. public class PSGenerator {
  38. /**
  39. * Indicator for the PostScript interpreter that the value is provided
  40. * later in the document (mostly in the %%Trailer section).
  41. */
  42. public static final AtendIndicator ATEND = new AtendIndicator() {
  43. };
  44. /** Line feed used by PostScript */
  45. public static final char LF = '\n';
  46. private OutputStream out;
  47. private boolean commentsEnabled = true;
  48. private Stack graphicsStateStack = new Stack();
  49. private PSState currentState;
  50. //private DecimalFormat df3 = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.US));
  51. private DecimalFormat df3 = new DecimalFormat("0.###", new DecimalFormatSymbols(Locale.US));
  52. private DecimalFormat df5 = new DecimalFormat("0.#####", new DecimalFormatSymbols(Locale.US));
  53. private StringBuffer tempBuffer = new StringBuffer(256);
  54. /** @see java.io.FilterOutputStream **/
  55. public PSGenerator(OutputStream out) {
  56. this.out = out;
  57. this.currentState = new PSState();
  58. //this.graphicsStateStack.push(this.currentState);
  59. }
  60. /**
  61. * Returns the OutputStream the PSGenerator writes to.
  62. * @return the OutputStream
  63. */
  64. public OutputStream getOutputStream() {
  65. return this.out;
  66. }
  67. /**
  68. * Returns the selected PostScript level.
  69. * (Hardcoded to level 2 for the moment.)
  70. * @return the PostScript level
  71. */
  72. public int getPSLevel() {
  73. return 2;
  74. }
  75. /**
  76. * Attempts to resolve the given URI. PSGenerator should be subclasses to provide more
  77. * sophisticated URI resolution.
  78. * @param uri URI to access
  79. * @return A {@link javax.xml.transform.Source} object, or null if the URI
  80. * cannot be resolved.
  81. */
  82. public Source resolveURI(String uri) {
  83. return new javax.xml.transform.stream.StreamSource(uri);
  84. }
  85. /**
  86. * Writes a newline character to the OutputStream.
  87. *
  88. * @throws IOException In case of an I/O problem
  89. */
  90. public final void newLine() throws IOException {
  91. out.write(LF);
  92. }
  93. /**
  94. * Formats a double value for PostScript output.
  95. *
  96. * @param value value to format
  97. * @return the formatted value
  98. */
  99. public String formatDouble(double value) {
  100. return df3.format(value);
  101. }
  102. /**
  103. * Formats a double value for PostScript output (higher resolution).
  104. *
  105. * @param value value to format
  106. * @return the formatted value
  107. */
  108. public String formatDouble5(double value) {
  109. return df5.format(value);
  110. }
  111. /**
  112. * Writes a PostScript command to the stream.
  113. *
  114. * @param cmd The PostScript code to be written.
  115. * @exception IOException In case of an I/O problem
  116. */
  117. public void write(String cmd) throws IOException {
  118. /* @todo Check disabled until clarification.
  119. if (cmd.length() > 255) {
  120. throw new RuntimeException("PostScript command exceeded limit of 255 characters");
  121. } */
  122. out.write(cmd.getBytes("US-ASCII"));
  123. }
  124. /**
  125. * Writes a PostScript command to the stream and ends the line.
  126. *
  127. * @param cmd The PostScript code to be written.
  128. * @exception IOException In case of an I/O problem
  129. */
  130. public void writeln(String cmd) throws IOException {
  131. write(cmd);
  132. newLine();
  133. }
  134. /**
  135. * Writes a comment to the stream and ends the line. Output of comments can
  136. * be disabled to reduce the size of the generated file.
  137. *
  138. * @param comment comment to write
  139. * @exception IOException In case of an I/O problem
  140. */
  141. public void commentln(String comment) throws IOException {
  142. if (this.commentsEnabled) {
  143. writeln(comment);
  144. }
  145. }
  146. /**
  147. * Writes encoded data to the PostScript stream.
  148. *
  149. * @param cmd The encoded PostScript code to be written.
  150. * @exception IOException In case of an I/O problem
  151. */
  152. public void writeByteArr(byte[] cmd) throws IOException {
  153. out.write(cmd);
  154. newLine();
  155. }
  156. /**
  157. * Flushes the OutputStream.
  158. *
  159. * @exception IOException In case of an I/O problem
  160. */
  161. public void flush() throws IOException {
  162. out.flush();
  163. }
  164. /**
  165. * Escapes a character conforming to the rules established in the PostScript
  166. * Language Reference (Search for "Literal Text Strings").
  167. * @param c character to escape
  168. * @param target target StringBuffer to write the escaped character to
  169. */
  170. public static final void escapeChar(char c, StringBuffer target) {
  171. if (c > 127) {
  172. target.append("\\");
  173. target.append(Integer.toOctalString(c));
  174. } else {
  175. switch (c) {
  176. case '\n':
  177. target.append("\\n");
  178. break;
  179. case '\r':
  180. target.append("\\r");
  181. break;
  182. case '\t':
  183. target.append("\\t");
  184. break;
  185. case '\b':
  186. target.append("\\b");
  187. break;
  188. case '\f':
  189. target.append("\\f");
  190. break;
  191. case '\\':
  192. target.append("\\\\");
  193. break;
  194. case '(':
  195. target.append("\\(");
  196. break;
  197. case ')':
  198. target.append("\\)");
  199. break;
  200. default:
  201. target.append(c);
  202. }
  203. }
  204. }
  205. /**
  206. * Converts text by applying escaping rules established in the DSC specs.
  207. * @param text Text to convert
  208. * @return String The resulting String
  209. */
  210. public static final String convertStringToDSC(String text) {
  211. return convertStringToDSC(text, false);
  212. }
  213. /**
  214. * Converts text by applying escaping rules established in the DSC specs.
  215. * @param text Text to convert
  216. * @param forceParentheses Force the use of parentheses
  217. * @return String The resulting String
  218. */
  219. public static final String convertStringToDSC(String text,
  220. boolean forceParentheses) {
  221. if ((text == null) || (text.length() == 0)) {
  222. return "()";
  223. } else {
  224. int initialSize = text.length();
  225. initialSize += initialSize / 2;
  226. StringBuffer sb = new StringBuffer(initialSize);
  227. if ((Long.getLong(text) != null)
  228. || (text.indexOf(' ') >= 0)
  229. || forceParentheses) {
  230. sb.append('(');
  231. for (int i = 0; i < text.length(); i++) {
  232. final char c = text.charAt(i);
  233. escapeChar(c, sb);
  234. }
  235. sb.append(')');
  236. return sb.toString();
  237. } else {
  238. return text;
  239. }
  240. }
  241. }
  242. /**
  243. * Writes a DSC comment to the output stream.
  244. * @param name Name of the DSC comment
  245. * @exception IOException In case of an I/O problem
  246. * @see org.apache.fop.render.ps.DSCConstants
  247. */
  248. public void writeDSCComment(String name) throws IOException {
  249. writeln("%%" + name);
  250. }
  251. /**
  252. * Writes a DSC comment to the output stream. The parameter to the DSC
  253. * comment can be any object. The object is converted to a String as
  254. * necessary.
  255. * @param name Name of the DSC comment
  256. * @param param Single parameter to the DSC comment
  257. * @exception IOException In case of an I/O problem
  258. * @see org.apache.fop.render.ps.DSCConstants
  259. */
  260. public void writeDSCComment(String name, Object param) throws IOException {
  261. writeDSCComment(name, new Object[] {param});
  262. }
  263. /**
  264. * Writes a DSC comment to the output stream. The parameters to the DSC
  265. * comment can be any object. The objects are converted to Strings as
  266. * necessary. Please see the source code to find out what parameters are
  267. * currently supported.
  268. * @param name Name of the DSC comment
  269. * @param params Array of parameters to the DSC comment
  270. * @exception IOException In case of an I/O problem
  271. * @see org.apache.fop.render.ps.DSCConstants
  272. */
  273. public void writeDSCComment(String name, Object[] params) throws IOException {
  274. tempBuffer.setLength(0);
  275. tempBuffer.append("%%");
  276. tempBuffer.append(name);
  277. if ((params != null) && (params.length > 0)) {
  278. tempBuffer.append(": ");
  279. for (int i = 0; i < params.length; i++) {
  280. if (i > 0) {
  281. tempBuffer.append(" ");
  282. }
  283. if (params[i] instanceof String) {
  284. tempBuffer.append(convertStringToDSC((String)params[i]));
  285. } else if (params[i] instanceof AtendIndicator) {
  286. tempBuffer.append("(atend)");
  287. } else if (params[i] instanceof Double) {
  288. tempBuffer.append(df3.format(params[i]));
  289. } else if (params[i] instanceof Number) {
  290. tempBuffer.append(params[i].toString());
  291. } else if (params[i] instanceof Date) {
  292. DateFormat datef = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  293. tempBuffer.append(convertStringToDSC(datef.format((Date)params[i])));
  294. } else if (params[i] instanceof PSResource) {
  295. tempBuffer.append(((PSResource)params[i]).getResourceSpecification());
  296. } else {
  297. throw new IllegalArgumentException("Unsupported parameter type: "
  298. + params[i].getClass().getName());
  299. }
  300. }
  301. }
  302. writeln(tempBuffer.toString());
  303. }
  304. /**
  305. * Saves the graphics state of the rendering engine.
  306. * @exception IOException In case of an I/O problem
  307. */
  308. public void saveGraphicsState() throws IOException {
  309. writeln("gsave");
  310. PSState state = new PSState(this.currentState, false);
  311. this.graphicsStateStack.push(this.currentState);
  312. this.currentState = state;
  313. }
  314. /**
  315. * Restores the last graphics state of the rendering engine.
  316. * @return true if the state was restored, false if there's a stack underflow.
  317. * @exception IOException In case of an I/O problem
  318. */
  319. public boolean restoreGraphicsState() throws IOException {
  320. if (this.graphicsStateStack.size() > 0) {
  321. writeln("grestore");
  322. this.currentState = (PSState)this.graphicsStateStack.pop();
  323. return true;
  324. } else {
  325. return false;
  326. }
  327. }
  328. /**
  329. * Returns the current graphics state.
  330. * @return the current graphics state
  331. */
  332. public PSState getCurrentState() {
  333. return this.currentState;
  334. }
  335. /**
  336. * Concats the transformation matrix.
  337. * @param a A part
  338. * @param b B part
  339. * @param c C part
  340. * @param d D part
  341. * @param e E part
  342. * @param f F part
  343. * @exception IOException In case of an I/O problem
  344. */
  345. public void concatMatrix(double a, double b,
  346. double c, double d,
  347. double e, double f) throws IOException {
  348. AffineTransform at = new AffineTransform(a, b, c, d, e, f);
  349. concatMatrix(at);
  350. }
  351. /**
  352. * Concats the transformations matrix.
  353. * @param matrix Matrix to use
  354. * @exception IOException In case of an I/O problem
  355. */
  356. public void concatMatrix(double[] matrix) throws IOException {
  357. concatMatrix(matrix[0], matrix[1],
  358. matrix[2], matrix[3],
  359. matrix[4], matrix[5]);
  360. }
  361. /**
  362. * Concats the transformations matric.
  363. * @param at the AffineTransform whose matrix to use
  364. * @exception IOException In case of an I/O problem
  365. */
  366. public void concatMatrix(AffineTransform at) throws IOException {
  367. double[] matrix = new double[6];
  368. at.getMatrix(matrix);
  369. getCurrentState().concatMatrix(at);
  370. writeln("[" + formatDouble5(matrix[0]) + " "
  371. + formatDouble5(matrix[1]) + " "
  372. + formatDouble5(matrix[2]) + " "
  373. + formatDouble5(matrix[3]) + " "
  374. + formatDouble5(matrix[4]) + " "
  375. + formatDouble5(matrix[5]) + "] concat");
  376. }
  377. /**
  378. * Adds a rectangle to the current path.
  379. * @param x upper left corner
  380. * @param y upper left corner
  381. * @param w width
  382. * @param h height
  383. * @exception IOException In case of an I/O problem
  384. */
  385. public void defineRect(double x, double y, double w, double h)
  386. throws IOException {
  387. writeln(formatDouble(x)
  388. + " " + formatDouble(y)
  389. + " " + formatDouble(w)
  390. + " " + formatDouble(h)
  391. + " re");
  392. }
  393. /**
  394. * Establishes the specified line cap style.
  395. * @param linecap the line cap style (0, 1 or 2) as defined by the setlinecap command.
  396. * @exception IOException In case of an I/O problem
  397. */
  398. public void useLineCap(int linecap) throws IOException {
  399. if (getCurrentState().useLineCap(linecap)) {
  400. writeln(linecap + " setlinecap");
  401. }
  402. }
  403. /**
  404. * Establishes the specified line width.
  405. * @param width the line width as defined by the setlinewidth command.
  406. * @exception IOException In case of an I/O problem
  407. */
  408. public void useLineWidth(double width) throws IOException {
  409. if (getCurrentState().useLineWidth(width)) {
  410. writeln(formatDouble(width) + " setlinewidth");
  411. }
  412. }
  413. /**
  414. * Establishes the specified dash pattern.
  415. * @param pattern the dash pattern as defined by the setdash command.
  416. * @exception IOException In case of an I/O problem
  417. */
  418. public void useDash(String pattern) throws IOException {
  419. if (pattern == null) {
  420. pattern = PSState.DEFAULT_DASH;
  421. }
  422. if (getCurrentState().useDash(pattern)) {
  423. writeln(pattern + " setdash");
  424. }
  425. }
  426. /**
  427. * Establishes the specified color (RGB).
  428. * @param col the color as defined by the setrgbcolor command.
  429. * @exception IOException In case of an I/O problem
  430. */
  431. public void useRGBColor(Color col) throws IOException {
  432. if (col == null) {
  433. col = PSState.DEFAULT_RGB_COLOR;
  434. }
  435. if (getCurrentState().useColor(col)) {
  436. float[] comps = col.getColorComponents(null);
  437. writeln(formatDouble(comps[0])
  438. + " " + formatDouble(comps[1])
  439. + " " + formatDouble(comps[2])
  440. + " setrgbcolor");
  441. }
  442. }
  443. /**
  444. * Establishes the specified font and size.
  445. * @param name name of the font for the "F" command (see FOP Std Proc Set)
  446. * @param size size of the font
  447. * @exception IOException In case of an I/O problem
  448. */
  449. public void useFont(String name, float size) throws IOException {
  450. if (getCurrentState().useFont(name, size)) {
  451. writeln(name + " " + formatDouble(size) + " F");
  452. }
  453. }
  454. private Set documentSuppliedResources;
  455. private Set documentNeededResources;
  456. private Set pageResources;
  457. /**
  458. * Notifies the generator that a new page has been started and that the page resource
  459. * set can be cleared.
  460. */
  461. public void notifyStartNewPage() {
  462. if (pageResources != null) {
  463. pageResources.clear();
  464. }
  465. }
  466. /**
  467. * Notifies the generator about the usage of a resource on the current page.
  468. * @param res the resource being used
  469. * @param needed true if this is a needed resource, false for a supplied resource
  470. */
  471. public void notifyResourceUsage(PSResource res, boolean needed) {
  472. if (pageResources == null) {
  473. pageResources = new java.util.HashSet();
  474. }
  475. pageResources.add(res);
  476. if (needed) {
  477. if (documentNeededResources == null) {
  478. documentNeededResources = new java.util.HashSet();
  479. }
  480. documentNeededResources.add(res);
  481. } else {
  482. if (documentSuppliedResources == null) {
  483. documentSuppliedResources = new java.util.HashSet();
  484. }
  485. documentSuppliedResources.add(res);
  486. }
  487. }
  488. /**
  489. * Indicates whether a particular resource is supplied, rather than needed.
  490. * @param res the resource
  491. * @return true if the resource is registered as being supplied.
  492. */
  493. public boolean isResourceSupplied(PSResource res) {
  494. return documentSuppliedResources.contains(res);
  495. }
  496. /**
  497. * Writes a DSC comment for the accumulated used resources, either at page level or
  498. * at document level.
  499. * @param pageLevel true if the DSC comment for the page level should be generated,
  500. * false for the document level (in the trailer)
  501. * @exception IOException In case of an I/O problem
  502. */
  503. public void writeResources(boolean pageLevel) throws IOException {
  504. if (pageLevel) {
  505. writeResourceComment(DSCConstants.PAGE_RESOURCES, pageResources);
  506. } else {
  507. writeResourceComment(DSCConstants.DOCUMENT_NEEDED_RESOURCES,
  508. documentNeededResources);
  509. writeResourceComment(DSCConstants.DOCUMENT_SUPPLIED_RESOURCES,
  510. documentSuppliedResources);
  511. }
  512. }
  513. private void writeResourceComment(String name, Set resources) throws IOException {
  514. if (resources == null || resources.size() == 0) {
  515. return;
  516. }
  517. tempBuffer.setLength(0);
  518. tempBuffer.append("%%");
  519. tempBuffer.append(name);
  520. tempBuffer.append(" ");
  521. boolean first = true;
  522. Iterator i = resources.iterator();
  523. while (i.hasNext()) {
  524. if (!first) {
  525. writeln(tempBuffer.toString());
  526. tempBuffer.setLength(0);
  527. tempBuffer.append("%%+ ");
  528. }
  529. PSResource res = (PSResource)i.next();
  530. tempBuffer.append(res.getResourceSpecification());
  531. first = false;
  532. }
  533. writeln(tempBuffer.toString());
  534. }
  535. /** Used for the ATEND constant. See there. */
  536. private static interface AtendIndicator {
  537. }
  538. }