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.

PCLGenerator.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /*
  2. * Copyright 2006 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.pcl;
  18. import java.awt.Color;
  19. import java.awt.Dimension;
  20. import java.awt.Graphics2D;
  21. import java.awt.color.ColorSpace;
  22. import java.awt.geom.AffineTransform;
  23. import java.awt.image.BufferedImage;
  24. import java.awt.image.ColorConvertOp;
  25. import java.awt.image.ColorModel;
  26. import java.awt.image.IndexColorModel;
  27. import java.awt.image.Raster;
  28. import java.awt.image.RenderedImage;
  29. import java.io.IOException;
  30. import java.io.OutputStream;
  31. import java.text.DecimalFormat;
  32. import java.text.DecimalFormatSymbols;
  33. import java.util.Locale;
  34. /**
  35. * This class provides methods for generating PCL print files.
  36. */
  37. public class PCLGenerator {
  38. /** The ESC (escape) character */
  39. public static final char ESC = '\033';
  40. /** A list of all supported resolutions in PCL (values in dpi) */
  41. public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600};
  42. private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
  43. private final DecimalFormat df2 = new DecimalFormat("0.##", symbols);
  44. private final DecimalFormat df4 = new DecimalFormat("0.####", symbols);
  45. private OutputStream out;
  46. /**
  47. * Main constructor.
  48. * @param out the OutputStream to write the PCL stream to
  49. */
  50. public PCLGenerator(OutputStream out) {
  51. this.out = out;
  52. }
  53. /** @return the OutputStream that this generator writes to */
  54. public OutputStream getOutputStream() {
  55. return this.out;
  56. }
  57. /**
  58. * Writes a PCL escape command to the output stream.
  59. * @param cmd the command (without the ESCAPE character)
  60. * @throws IOException In case of an I/O error
  61. */
  62. public void writeCommand(String cmd) throws IOException {
  63. out.write(27); //ESC
  64. out.write(cmd.getBytes("US-ASCII"));
  65. }
  66. /**
  67. * Writes raw text (in ISO-8859-1 encoding) to the output stream.
  68. * @param s the text
  69. * @throws IOException In case of an I/O error
  70. */
  71. public void writeText(String s) throws IOException {
  72. out.write(s.getBytes("ISO-8859-1"));
  73. }
  74. /**
  75. * Formats a double value with two decimal positions for PCL output.
  76. *
  77. * @param value value to format
  78. * @return the formatted value
  79. */
  80. public final String formatDouble2(double value) {
  81. return df2.format(value);
  82. }
  83. /**
  84. * Formats a double value with four decimal positions for PCL output.
  85. *
  86. * @param value value to format
  87. * @return the formatted value
  88. */
  89. public final String formatDouble4(double value) {
  90. return df4.format(value);
  91. }
  92. /**
  93. * Sends the universal end of language command (UEL).
  94. * @throws IOException In case of an I/O error
  95. */
  96. public void universalEndOfLanguage() throws IOException {
  97. writeCommand("%-12345X");
  98. }
  99. /**
  100. * Resets the printer and restores the user default environment.
  101. * @throws IOException In case of an I/O error
  102. */
  103. public void resetPrinter() throws IOException {
  104. writeCommand("E");
  105. }
  106. /**
  107. * Sends the job separation command.
  108. * @throws IOException In case of an I/O error
  109. */
  110. public void separateJobs() throws IOException {
  111. writeCommand("&l1T");
  112. }
  113. /**
  114. * Sends the form feed character.
  115. * @throws IOException In case of an I/O error
  116. */
  117. public void formFeed() throws IOException {
  118. out.write(12); //=OC ("FF", Form feed)
  119. }
  120. /**
  121. * Clears the horizontal margins.
  122. * @throws IOException In case of an I/O error
  123. */
  124. public void clearHorizontalMargins() throws IOException {
  125. writeCommand("9");
  126. }
  127. /**
  128. * The Top Margin command designates the number of lines between
  129. * the top of the logical page and the top of the text area.
  130. * @param numberOfLines the number of lines (See PCL specification for details)
  131. * @throws IOException In case of an I/O error
  132. */
  133. public void setTopMargin(int numberOfLines) throws IOException {
  134. writeCommand("&l" + numberOfLines + "E");
  135. }
  136. /**
  137. * Sets the cursor to a new absolute coordinate.
  138. * @param x the X coordinate (in millipoints)
  139. * @param y the Y coordinate (in millipoints)
  140. * @throws IOException In case of an I/O error
  141. */
  142. public void setCursorPos(int x, int y) throws IOException {
  143. writeCommand("*p" + (x / 100) + "h" + (y / 100) + "V");
  144. }
  145. /**
  146. * Generate a filled rectangle
  147. *
  148. * @param x the x position of left edge in millipoints
  149. * @param y the y position of top edge in millipoints
  150. * @param w the width in millipoints
  151. * @param h the height in millipoints
  152. * @param col the fill color
  153. * @throws IOException In case of an I/O error
  154. */
  155. protected void fillRect(int x, int y, int w, int h, Color col) throws IOException {
  156. if ((w == 0) || (h == 0)) {
  157. return;
  158. }
  159. if (h < 0) {
  160. h *= -1;
  161. } else {
  162. //y += h;
  163. }
  164. int xpos = (x / 100);
  165. if (xpos < 0) {
  166. //A negative x coordinate can lead to a displaced rectangle (xpos must be >= 0)
  167. w += x;
  168. xpos = 0;
  169. }
  170. writeCommand("*v1O");
  171. writeCommand("&a" + formatDouble4(xpos) + "h"
  172. + formatDouble4(y / 100) + "V");
  173. writeCommand("*c" + formatDouble4(w / 100) + "h"
  174. + formatDouble4(h / 100) + "V");
  175. int lineshade = convertToPCLShade(col);
  176. writeCommand("*c" + lineshade + "G");
  177. writeCommand("*c2P");
  178. // Reset pattern transparency mode.
  179. writeCommand("*v0O");
  180. }
  181. /**
  182. * Sets the pattern transparency mode.
  183. * @param transparent true if transparent, false for opaque
  184. * @throws IOException In case of an I/O error
  185. */
  186. public void setPatternTransparencyMode(boolean transparent) throws IOException {
  187. if (transparent) {
  188. writeCommand("*v0O");
  189. } else {
  190. writeCommand("*v1O");
  191. }
  192. }
  193. /**
  194. * Convert an RGB color value to a grayscale from 0 to 100.
  195. * @param r the red component
  196. * @param g the green component
  197. * @param b the blue component
  198. * @return the gray value
  199. */
  200. public final int convertToGray(int r, int g, int b) {
  201. return (r * 30 + g * 59 + b * 11) / 100;
  202. }
  203. /**
  204. * Convert a Color value to a PCL shade value (0-100).
  205. * @param col the color
  206. * @return the PCL shade value (100=black)
  207. */
  208. public final int convertToPCLShade(Color col) {
  209. float gray = convertToGray(col.getRed(), col.getGreen(), col.getBlue()) / 255f;
  210. return (int)(100 - (gray * 100f));
  211. }
  212. /**
  213. * Select the current pattern
  214. * @param patternID the pattern ID (<ESC>*c#G command)
  215. * @param pattern the pattern type (<ESC>*v#T command)
  216. * @throws IOException In case of an I/O error
  217. */
  218. public void selectCurrentPattern(int patternID, int pattern) throws IOException {
  219. writeCommand("*c" + patternID + "G");
  220. writeCommand("*v" + pattern + "T");
  221. }
  222. /**
  223. * Indicates whether an image is a monochrome (b/w) image.
  224. * @param img the image
  225. * @return true if it's a monochrome image
  226. */
  227. public static boolean isMonochromeImage(RenderedImage img) {
  228. ColorModel cm = img.getColorModel();
  229. if (cm instanceof IndexColorModel) {
  230. IndexColorModel icm = (IndexColorModel)cm;
  231. return icm.getMapSize() == 2;
  232. } else {
  233. return false;
  234. }
  235. }
  236. /**
  237. * Indicates whether an image is a grayscale image.
  238. * @param img the image
  239. * @return true if it's a grayscale image
  240. */
  241. public static boolean isGrayscaleImage(RenderedImage img) {
  242. return (img.getColorModel().getColorSpace().getNumComponents() == 1);
  243. }
  244. private MonochromeBitmapConverter createMonochromeBitmapConverter() {
  245. MonochromeBitmapConverter converter = null;
  246. try {
  247. String clName = "org.apache.fop.render.pcl.JAIMonochromeBitmapConverter";
  248. Class clazz = Class.forName(clName);
  249. converter = (MonochromeBitmapConverter)clazz.newInstance();
  250. } catch (ClassNotFoundException cnfe) {
  251. // Class was not compiled so is not available. Simply ignore.
  252. } catch (LinkageError le) {
  253. // This can happen if fop was build with support for a
  254. // particular provider (e.g. a binary fop distribution)
  255. // but the required support files (i.e. JAI) are not
  256. // available in the current runtime environment.
  257. // Simply continue with the backup implementation.
  258. } catch (InstantiationException e) {
  259. // Problem instantiating the class, simply continue with the backup implementation
  260. } catch (IllegalAccessException e) {
  261. // Problem instantiating the class, simply continue with the backup implementation
  262. }
  263. if (converter == null) {
  264. converter = new DefaultMonochromeBitmapConverter();
  265. }
  266. return converter;
  267. }
  268. private int calculatePCLResolution(int resolution) {
  269. return calculatePCLResolution(resolution, false);
  270. }
  271. /**
  272. * Calculates the ideal PCL resolution for a given resolution.
  273. * @param resolution the input resolution
  274. * @param increased true if you want to go to a higher resolution, for example if you
  275. * convert grayscale or color images to monochrome images so dithering has
  276. * a chance to generate better quality.
  277. * @return the resulting PCL resolution (one of 75, 100, 150, 200, 300, 600)
  278. */
  279. private int calculatePCLResolution(int resolution, boolean increased) {
  280. for (int i = PCL_RESOLUTIONS.length - 2; i >= 0; i--) {
  281. if (resolution > PCL_RESOLUTIONS[i]) {
  282. int idx = i + 1;
  283. if (idx < PCL_RESOLUTIONS.length - 2) {
  284. idx += increased ? 2 : 0;
  285. } else if (idx < PCL_RESOLUTIONS.length - 1) {
  286. idx += increased ? 1 : 0;
  287. }
  288. return PCL_RESOLUTIONS[idx];
  289. }
  290. }
  291. return PCL_RESOLUTIONS[increased ? 2 : 0];
  292. }
  293. private boolean isValidPCLResolution(int resolution) {
  294. return resolution == calculatePCLResolution(resolution);
  295. }
  296. private Dimension getAdjustedDimension(Dimension orgDim, int orgResolution,
  297. int pclResolution) {
  298. if (orgResolution == pclResolution) {
  299. return orgDim;
  300. } else {
  301. Dimension result = new Dimension();
  302. result.width = (int)Math.round((double)orgDim.width * pclResolution / orgResolution);
  303. result.height = (int)Math.round((double)orgDim.height * pclResolution / orgResolution);
  304. return result;
  305. }
  306. }
  307. /**
  308. * Paint a bitmap at the current cursor position. The bitmap is converted to a monochrome
  309. * (1-bit) bitmap image.
  310. * @param img the bitmap image
  311. * @param resolution the original resolution of the image (in dpi)
  312. * @throws IOException In case of an I/O error
  313. */
  314. public void paintBitmap(RenderedImage img, int resolution) throws IOException {
  315. boolean monochrome = isMonochromeImage(img);
  316. if (!monochrome) {
  317. int effResolution = calculatePCLResolution(resolution, true);
  318. Dimension orgDim = new Dimension(img.getWidth(), img.getHeight());
  319. Dimension effDim = getAdjustedDimension(orgDim, resolution, effResolution);
  320. boolean scaled = !orgDim.equals(effDim);
  321. BufferedImage src = null;
  322. if (img instanceof BufferedImage && !scaled) {
  323. if (!isGrayscaleImage(img)) {
  324. src = new BufferedImage(effDim.width, effDim.height,
  325. BufferedImage.TYPE_BYTE_GRAY);
  326. ColorConvertOp op = new ColorConvertOp(
  327. ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
  328. op.filter((BufferedImage)img, src);
  329. } else {
  330. src = (BufferedImage)img;
  331. }
  332. }
  333. if (src == null) {
  334. src = new BufferedImage(effDim.width, effDim.height,
  335. BufferedImage.TYPE_BYTE_GRAY);
  336. Graphics2D g2d = src.createGraphics();
  337. try {
  338. AffineTransform at = new AffineTransform();
  339. double sx = effDim.getWidth() / orgDim.getWidth();
  340. double sy = effDim.getHeight() / orgDim.getHeight();
  341. at.scale(sx, sy);
  342. g2d.drawRenderedImage(img, at);
  343. } finally {
  344. g2d.dispose();
  345. }
  346. }
  347. MonochromeBitmapConverter converter = createMonochromeBitmapConverter();
  348. converter.setHint("quality", "false");
  349. long start = System.currentTimeMillis();
  350. BufferedImage buf = (BufferedImage)converter.convertToMonochrome(src);
  351. long duration = System.currentTimeMillis() - start;
  352. System.out.println(duration + " ms");
  353. RenderedImage red = buf;
  354. paintMonochromeBitmap(red, effResolution);
  355. } else {
  356. int effResolution = calculatePCLResolution(resolution);
  357. paintMonochromeBitmap(img, effResolution);
  358. }
  359. }
  360. /**
  361. * Paint a bitmap at the current cursor position. The bitmap must be a monochrome
  362. * (1-bit) bitmap image.
  363. * @param img the bitmap image (must be 1-bit b/w)
  364. * @param resolution the resolution of the image (must be a PCL resolution)
  365. * @throws IOException In case of an I/O error
  366. */
  367. public void paintMonochromeBitmap(RenderedImage img, int resolution) throws IOException {
  368. if (!isValidPCLResolution(resolution)) {
  369. throw new IllegalArgumentException("Invalid PCL resolution: " + resolution);
  370. }
  371. writeCommand("*t" + resolution + "R");
  372. writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A");
  373. Raster raster = img.getData();
  374. boolean monochrome = isMonochromeImage(img);
  375. if (!monochrome) {
  376. throw new IllegalArgumentException("img must be a monochrome image");
  377. }
  378. int x = 0;
  379. int y = 0;
  380. int imgw = img.getWidth();
  381. int imgh = img.getHeight();
  382. int bytewidth = (imgw / 8);
  383. if ((imgw % 8) != 0) {
  384. bytewidth++;
  385. }
  386. byte ib;
  387. byte[] rle = new byte[bytewidth * 2]; //compressed (RLE)
  388. byte[] uncompressed = new byte[bytewidth]; //uncompressed
  389. int lastcount = -1;
  390. byte lastbyte = 0;
  391. int rlewidth = 0;
  392. /*
  393. int xres = (iw * 72000) / w;
  394. int yres = (ih * 72000) / h;
  395. int resolution = xres;
  396. if (yres > xres)
  397. resolution = yres;
  398. if (resolution > 300)
  399. resolution = 600;
  400. else if (resolution > 150)
  401. resolution = 300;
  402. else if (resolution > 100)
  403. resolution = 150;
  404. else if (resolution > 75)
  405. resolution = 100;
  406. else
  407. resolution = 75;
  408. */
  409. // Transfer graphics data
  410. for (y = 0; y < imgh; y++) {
  411. ib = 0;
  412. for (x = 0; x < imgw; x++) {
  413. int sample = raster.getSample(x, y, 0);
  414. //Set image bit for black
  415. if ((sample == 0)) {
  416. ib |= (1 << (7 - (x % 8)));
  417. }
  418. //RLE encoding
  419. if ((x % 8) == 7 || ((x + 1) == imgw)) {
  420. if (rlewidth < bytewidth) {
  421. if (lastcount >= 0) {
  422. if (ib == lastbyte) {
  423. lastcount++;
  424. } else {
  425. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  426. rle[rlewidth++] = lastbyte;
  427. lastbyte = ib;
  428. lastcount = 0;
  429. }
  430. } else {
  431. lastbyte = ib;
  432. lastcount = 0;
  433. }
  434. if (lastcount == 255 || ((x + 1) == imgw)) {
  435. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  436. rle[rlewidth++] = lastbyte;
  437. lastbyte = 0;
  438. lastcount = -1;
  439. }
  440. }
  441. uncompressed[x / 8] = ib;
  442. ib = 0;
  443. }
  444. }
  445. if (rlewidth < bytewidth) {
  446. writeCommand("*b1m" + rlewidth + "W");
  447. this.out.write(rle, 0, rlewidth);
  448. } else {
  449. writeCommand("*b0m" + bytewidth + "W");
  450. this.out.write(uncompressed);
  451. }
  452. lastcount = -1;
  453. rlewidth = 0;
  454. }
  455. // End raster graphics
  456. writeCommand("*rB");
  457. }
  458. }