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 47KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.render.pcl;
  19. import java.awt.Color;
  20. import java.awt.Dimension;
  21. import java.awt.Graphics2D;
  22. import java.awt.Image;
  23. import java.awt.image.BufferedImage;
  24. import java.awt.image.ColorModel;
  25. import java.awt.image.DataBuffer;
  26. import java.awt.image.DataBufferByte;
  27. import java.awt.image.DataBufferInt;
  28. import java.awt.image.DirectColorModel;
  29. import java.awt.image.IndexColorModel;
  30. import java.awt.image.MultiPixelPackedSampleModel;
  31. import java.awt.image.Raster;
  32. import java.awt.image.RenderedImage;
  33. import java.awt.image.SinglePixelPackedSampleModel;
  34. import java.io.DataOutputStream;
  35. import java.io.IOException;
  36. import java.io.OutputStream;
  37. import java.text.DecimalFormat;
  38. import java.text.DecimalFormatSymbols;
  39. import java.util.HashMap;
  40. import java.util.LinkedHashMap;
  41. import java.util.Locale;
  42. import java.util.Map;
  43. import org.apache.commons.io.IOUtils;
  44. import org.apache.commons.io.output.ByteArrayOutputStream;
  45. import org.apache.commons.io.output.CountingOutputStream;
  46. import org.apache.xmlgraphics.util.UnitConv;
  47. import org.apache.fop.fonts.Typeface;
  48. import org.apache.fop.render.pcl.fonts.PCLFontReader;
  49. import org.apache.fop.render.pcl.fonts.PCLSoftFontManager;
  50. import org.apache.fop.util.bitmap.BitmapImageUtil;
  51. import org.apache.fop.util.bitmap.DitherUtil;
  52. /**
  53. * This class provides methods for generating PCL print files.
  54. */
  55. public class PCLGenerator {
  56. private static final String US_ASCII = "US-ASCII";
  57. private static final String ISO_8859_1 = "ISO-8859-1";
  58. /** The ESC (escape) character */
  59. public static final char ESC = '\033';
  60. /** A list of all supported resolutions in PCL (values in dpi) */
  61. public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600};
  62. private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
  63. private final DecimalFormat df2 = new DecimalFormat("0.##", symbols);
  64. private final DecimalFormat df4 = new DecimalFormat("0.####", symbols);
  65. private final CountingOutputStream out;
  66. protected Map<Typeface, PCLFontReader> fontReaderMap = new HashMap<Typeface, PCLFontReader>();
  67. protected Map<PCLSoftFontManager, Map<Typeface, Long>> fontManagerMap
  68. = new LinkedHashMap<PCLSoftFontManager, Map<Typeface, Long>>();
  69. private boolean currentSourceTransparency = true;
  70. private boolean currentPatternTransparency = true;
  71. private int maxBitmapResolution = PCL_RESOLUTIONS[PCL_RESOLUTIONS.length - 1];
  72. private float ditheringQuality = 0.5f;
  73. /**
  74. * true: Standard PCL shades are used (poor quality). false: user-defined pattern are used
  75. * to create custom dither patterns for better grayscale quality.
  76. */
  77. private static final boolean USE_PCL_SHADES = false;
  78. /**
  79. * Main constructor.
  80. * @param out the OutputStream to write the PCL stream to
  81. */
  82. public PCLGenerator(OutputStream out) {
  83. this.out = new CountingOutputStream(out);
  84. }
  85. /**
  86. * Main constructor.
  87. * @param out the OutputStream to write the PCL stream to
  88. * @param maxResolution the maximum resolution to encode bitmap images at
  89. */
  90. public PCLGenerator(OutputStream out, int maxResolution) {
  91. this(out);
  92. boolean found = false;
  93. for (int pclResolutions : PCL_RESOLUTIONS) {
  94. if (pclResolutions == maxResolution) {
  95. found = true;
  96. break;
  97. }
  98. }
  99. if (!found) {
  100. throw new IllegalArgumentException("Illegal value for maximum resolution!");
  101. }
  102. this.maxBitmapResolution = maxResolution;
  103. }
  104. public void addFont(PCLSoftFontManager sfManager, Typeface font) {
  105. if (!fontManagerMap.containsKey(sfManager)) {
  106. fontManagerMap.put(sfManager, new LinkedHashMap<Typeface, Long>());
  107. }
  108. Map<Typeface, Long> fonts = fontManagerMap.get(sfManager);
  109. if (!fonts.containsKey(font)) {
  110. fonts.put(font, out.getByteCount());
  111. }
  112. }
  113. /** @return the OutputStream that this generator writes to */
  114. public OutputStream getOutputStream() {
  115. return this.out;
  116. }
  117. /**
  118. * Returns the currently active text encoding.
  119. * @return the text encoding
  120. */
  121. public String getTextEncoding() {
  122. return ISO_8859_1;
  123. }
  124. /** @return the maximum resolution to encode bitmap images at */
  125. public int getMaximumBitmapResolution() {
  126. return this.maxBitmapResolution;
  127. }
  128. /**
  129. * Writes a PCL escape command to the output stream.
  130. * @param cmd the command (without the ESCAPE character)
  131. * @throws IOException In case of an I/O error
  132. */
  133. public void writeCommand(String cmd) throws IOException {
  134. out.write(27); //ESC
  135. out.write(cmd.getBytes(US_ASCII));
  136. }
  137. /**
  138. * Writes raw text (in ISO-8859-1 encoding) to the output stream.
  139. * @param s the text
  140. * @throws IOException In case of an I/O error
  141. */
  142. public void writeText(String s) throws IOException {
  143. out.write(s.getBytes(ISO_8859_1));
  144. }
  145. /**
  146. * Writes raw bytes to the output stream
  147. * @param bytes The bytes
  148. * @throws IOException In case of an I/O error
  149. */
  150. public void writeBytes(byte[] bytes) throws IOException {
  151. out.write(bytes);
  152. }
  153. /**
  154. * Formats a double value with two decimal positions for PCL output.
  155. *
  156. * @param value value to format
  157. * @return the formatted value
  158. */
  159. public final String formatDouble2(double value) {
  160. return df2.format(value);
  161. }
  162. /**
  163. * Formats a double value with four decimal positions for PCL output.
  164. *
  165. * @param value value to format
  166. * @return the formatted value
  167. */
  168. public final String formatDouble4(double value) {
  169. return df4.format(value);
  170. }
  171. /**
  172. * Sends the universal end of language command (UEL).
  173. * @throws IOException In case of an I/O error
  174. */
  175. public void universalEndOfLanguage() throws IOException {
  176. writeCommand("%-12345X");
  177. }
  178. /**
  179. * Resets the printer and restores the user default environment.
  180. * @throws IOException In case of an I/O error
  181. */
  182. public void resetPrinter() throws IOException {
  183. writeCommand("E");
  184. }
  185. /**
  186. * Sends the job separation command.
  187. * @throws IOException In case of an I/O error
  188. */
  189. public void separateJobs() throws IOException {
  190. writeCommand("&l1T");
  191. }
  192. /**
  193. * Sends the form feed character.
  194. * @throws IOException In case of an I/O error
  195. */
  196. public void formFeed() throws IOException {
  197. out.write(12); //=OC ("FF", Form feed)
  198. }
  199. /**
  200. * Sets the unit of measure.
  201. * @param value the resolution value (units per inch)
  202. * @throws IOException In case of an I/O error
  203. */
  204. public void setUnitOfMeasure(int value) throws IOException {
  205. writeCommand("&u" + value + "D");
  206. }
  207. /**
  208. * Sets the raster graphics resolution
  209. * @param value the resolution value (units per inch)
  210. * @throws IOException In case of an I/O error
  211. */
  212. public void setRasterGraphicsResolution(int value) throws IOException {
  213. writeCommand("*t" + value + "R");
  214. }
  215. /**
  216. * Selects the page size.
  217. * @param selector the integer representing the page size
  218. * @throws IOException In case of an I/O error
  219. */
  220. public void selectPageSize(int selector) throws IOException {
  221. writeCommand("&l" + selector + "A");
  222. }
  223. /**
  224. * Selects the paper source. The parameter is usually printer-specific. Usually, "1" is the
  225. * default tray, "2" is the manual paper feed, "3" is the manual envelope feed, "4" is the
  226. * "lower" tray and "7" is "auto-select". Consult the technical reference for your printer
  227. * for all available values.
  228. * @param selector the integer representing the paper source/tray
  229. * @throws IOException In case of an I/O error
  230. */
  231. public void selectPaperSource(int selector) throws IOException {
  232. writeCommand("&l" + selector + "H");
  233. }
  234. /**
  235. * Selects the output bin. The parameter is usually printer-specific. Usually, "1" is the
  236. * default output bin (upper bin) and "2" is the lower (rear) output bin. Some printers
  237. * may support additional output bins. Consult the technical reference for your printer
  238. * for all available values.
  239. * @param selector the integer representing the output bin
  240. * @throws IOException In case of an I/O error
  241. */
  242. public void selectOutputBin(int selector) throws IOException {
  243. writeCommand("&l" + selector + "G");
  244. }
  245. /**
  246. * Selects the duplexing mode for the page.
  247. * The parameter is usually printer-specific.
  248. * "0" means Simplex,
  249. * "1" means Duplex, Long-Edge Binding,
  250. * "2" means Duplex, Short-Edge Binding.
  251. * @param selector the integer representing the duplexing mode of the page
  252. * @throws IOException In case of an I/O error
  253. */
  254. public void selectDuplexMode(int selector) throws IOException {
  255. writeCommand("&l" + selector + "S");
  256. }
  257. /**
  258. * Clears the horizontal margins.
  259. * @throws IOException In case of an I/O error
  260. */
  261. public void clearHorizontalMargins() throws IOException {
  262. writeCommand("9");
  263. }
  264. /**
  265. * The Top Margin command designates the number of lines between
  266. * the top of the logical page and the top of the text area.
  267. * @param numberOfLines the number of lines (See PCL specification for details)
  268. * @throws IOException In case of an I/O error
  269. */
  270. public void setTopMargin(int numberOfLines) throws IOException {
  271. writeCommand("&l" + numberOfLines + "E");
  272. }
  273. /**
  274. * The Text Length command can be used to define the bottom border. See the PCL specification
  275. * for details.
  276. * @param numberOfLines the number of lines
  277. * @throws IOException In case of an I/O error
  278. */
  279. public void setTextLength(int numberOfLines) throws IOException {
  280. writeCommand("&l" + numberOfLines + "F");
  281. }
  282. /**
  283. * Sets the Vertical Motion Index (VMI).
  284. * @param value the VMI value
  285. * @throws IOException In case of an I/O error
  286. */
  287. public void setVMI(double value) throws IOException {
  288. writeCommand("&l" + formatDouble4(value) + "C");
  289. }
  290. /**
  291. * Sets the cursor to a new absolute coordinate.
  292. * @param x the X coordinate (in millipoints)
  293. * @param y the Y coordinate (in millipoints)
  294. * @throws IOException In case of an I/O error
  295. */
  296. public void setCursorPos(double x, double y) throws IOException {
  297. if (x < 0) {
  298. //A negative x value will result in a relative movement so go to "0" first.
  299. //But this will most probably have no effect anyway since you can't paint to the left
  300. //of the logical page
  301. writeCommand("&a0h" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V");
  302. } else {
  303. writeCommand("&a" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V");
  304. }
  305. }
  306. /**
  307. * Pushes the current cursor position on a stack (stack size: max 20 entries)
  308. * @throws IOException In case of an I/O error
  309. */
  310. public void pushCursorPos() throws IOException {
  311. writeCommand("&f0S");
  312. }
  313. /**
  314. * Pops the current cursor position from the stack.
  315. * @throws IOException In case of an I/O error
  316. */
  317. public void popCursorPos() throws IOException {
  318. writeCommand("&f1S");
  319. }
  320. /**
  321. * Changes the current print direction while maintaining the current cursor position.
  322. * @param rotate the rotation angle (counterclockwise), one of 0, 90, 180 and 270.
  323. * @throws IOException In case of an I/O error
  324. */
  325. public void changePrintDirection(int rotate) throws IOException {
  326. writeCommand("&a" + rotate + "P");
  327. }
  328. /**
  329. * Enters the HP GL/2 mode.
  330. * @param restorePreviousHPGL2Cursor true if the previous HP GL/2 pen position should be
  331. * restored, false if the current position is maintained
  332. * @throws IOException In case of an I/O error
  333. */
  334. public void enterHPGL2Mode(boolean restorePreviousHPGL2Cursor) throws IOException {
  335. if (restorePreviousHPGL2Cursor) {
  336. writeCommand("%0B");
  337. } else {
  338. writeCommand("%1B");
  339. }
  340. }
  341. /**
  342. * Enters the PCL mode.
  343. * @param restorePreviousPCLCursor true if the previous PCL cursor position should be restored,
  344. * false if the current position is maintained
  345. * @throws IOException In case of an I/O error
  346. */
  347. public void enterPCLMode(boolean restorePreviousPCLCursor) throws IOException {
  348. if (restorePreviousPCLCursor) {
  349. writeCommand("%0A");
  350. } else {
  351. writeCommand("%1A");
  352. }
  353. }
  354. /**
  355. * Generate a filled rectangle at the current cursor position.
  356. *
  357. * @param w the width in millipoints
  358. * @param h the height in millipoints
  359. * @param col the fill color
  360. * @throws IOException In case of an I/O error
  361. */
  362. protected void fillRect(int w, int h, Color col, boolean colorEnabled) throws IOException {
  363. if ((w == 0) || (h == 0)) {
  364. return;
  365. }
  366. if (h < 0) {
  367. h *= -1;
  368. } else {
  369. //y += h;
  370. }
  371. setPatternTransparencyMode(false);
  372. if (USE_PCL_SHADES
  373. || Color.black.equals(col)
  374. || Color.white.equals(col)) {
  375. writeCommand("*c" + formatDouble4(w / 100.0) + "h"
  376. + formatDouble4(h / 100.0) + "V");
  377. int lineshade = convertToPCLShade(col);
  378. writeCommand("*c" + lineshade + "G");
  379. writeCommand("*c2P"); //Shaded fill
  380. } else {
  381. if (colorEnabled) {
  382. selectColor(col);
  383. writeCommand("*c" + formatDouble4(w / 100.0) + "h"
  384. + formatDouble4(h / 100.0) + "V");
  385. writeCommand("*c0P"); //Solid fill
  386. } else {
  387. defineGrayscalePattern(col, 32, DitherUtil.DITHER_MATRIX_4X4);
  388. writeCommand("*c" + formatDouble4(w / 100.0) + "h"
  389. + formatDouble4(h / 100.0) + "V");
  390. writeCommand("*c32G");
  391. writeCommand("*c4P"); //User-defined pattern
  392. }
  393. }
  394. // Reset pattern transparency mode.
  395. setPatternTransparencyMode(true);
  396. }
  397. /**
  398. * Generates a user-defined pattern for a dithering pattern matching the grayscale value
  399. * of the color given.
  400. * @param col the color to create the pattern for
  401. * @param patternID the pattern ID to use
  402. * @param ditherMatrixSize the size of the Bayer dither matrix to use (4 or 8 supported)
  403. * @throws IOException In case of an I/O error
  404. */
  405. public void defineGrayscalePattern(Color col, int patternID, int ditherMatrixSize)
  406. throws IOException {
  407. ByteArrayOutputStream baout = new ByteArrayOutputStream();
  408. DataOutputStream data = new DataOutputStream(baout);
  409. data.writeByte(0); //Format
  410. data.writeByte(0); //Continuation
  411. data.writeByte(1); //Pixel Encoding
  412. data.writeByte(0); //Reserved
  413. data.writeShort(8); //Width in Pixels
  414. data.writeShort(8); //Height in Pixels
  415. //data.writeShort(600); //X Resolution (didn't manage to get that to work)
  416. //data.writeShort(600); //Y Resolution
  417. int gray255 = convertToGray(col.getRed(), col.getGreen(), col.getBlue());
  418. byte[] pattern;
  419. if (ditherMatrixSize == 8) {
  420. pattern = DitherUtil.getBayerDither(DitherUtil.DITHER_MATRIX_8X8, gray255, false);
  421. } else {
  422. //Since a 4x4 pattern did not work, the 4x4 pattern is applied 4 times to an
  423. //8x8 pattern. Maybe this could be changed to use an 8x8 bayer dither pattern
  424. //instead of the 4x4 one.
  425. pattern = DitherUtil.getBayerDither(DitherUtil.DITHER_MATRIX_4X4, gray255, true);
  426. }
  427. data.write(pattern);
  428. if ((baout.size() % 2) > 0) {
  429. baout.write(0);
  430. }
  431. writeCommand("*c" + patternID + "G");
  432. writeCommand("*c" + baout.size() + "W");
  433. baout.writeTo(this.out);
  434. IOUtils.closeQuietly(data);
  435. IOUtils.closeQuietly(baout);
  436. writeCommand("*c4Q"); //temporary pattern
  437. }
  438. /**
  439. * Sets the source transparency mode.
  440. * @param transparent true if transparent, false for opaque
  441. * @throws IOException In case of an I/O error
  442. */
  443. public void setSourceTransparencyMode(boolean transparent) throws IOException {
  444. setTransparencyMode(transparent, currentPatternTransparency);
  445. }
  446. /**
  447. * Sets the pattern transparency mode.
  448. * @param transparent true if transparent, false for opaque
  449. * @throws IOException In case of an I/O error
  450. */
  451. public void setPatternTransparencyMode(boolean transparent) throws IOException {
  452. setTransparencyMode(currentSourceTransparency, transparent);
  453. }
  454. /**
  455. * Sets the transparency modes.
  456. * @param source source transparency: true if transparent, false for opaque
  457. * @param pattern pattern transparency: true if transparent, false for opaque
  458. * @throws IOException In case of an I/O error
  459. */
  460. public void setTransparencyMode(boolean source, boolean pattern) throws IOException {
  461. if (source != currentSourceTransparency && pattern != currentPatternTransparency) {
  462. writeCommand("*v" + (source ? '0' : '1') + "n" + (pattern ? '0' : '1') + "O");
  463. } else if (source != currentSourceTransparency) {
  464. writeCommand("*v" + (source ? '0' : '1') + "N");
  465. } else if (pattern != currentPatternTransparency) {
  466. writeCommand("*v" + (pattern ? '0' : '1') + "O");
  467. }
  468. this.currentSourceTransparency = source;
  469. this.currentPatternTransparency = pattern;
  470. }
  471. /**
  472. * Convert an RGB color value to a grayscale from 0 to 100.
  473. * @param r the red component
  474. * @param g the green component
  475. * @param b the blue component
  476. * @return the gray value
  477. */
  478. public final int convertToGray(int r, int g, int b) {
  479. return BitmapImageUtil.convertToGray(r, g, b);
  480. }
  481. /**
  482. * Convert a Color value to a PCL shade value (0-100).
  483. * @param col the color
  484. * @return the PCL shade value (100=black)
  485. */
  486. public final int convertToPCLShade(Color col) {
  487. float gray = convertToGray(col.getRed(), col.getGreen(), col.getBlue()) / 255f;
  488. return (int)(100 - (gray * 100f));
  489. }
  490. /**
  491. * Selects the current grayscale color (the given color is converted to grayscales).
  492. * @param col the color
  493. * @throws IOException In case of an I/O error
  494. */
  495. public void selectGrayscale(Color col) throws IOException {
  496. if (Color.black.equals(col)) {
  497. selectCurrentPattern(0, 0); //black
  498. } else if (Color.white.equals(col)) {
  499. selectCurrentPattern(0, 1); //white
  500. } else {
  501. if (USE_PCL_SHADES) {
  502. selectCurrentPattern(convertToPCLShade(col), 2);
  503. } else {
  504. defineGrayscalePattern(col, 32, DitherUtil.DITHER_MATRIX_4X4);
  505. selectCurrentPattern(32, 4);
  506. }
  507. }
  508. }
  509. public void selectColor(Color col) throws IOException {
  510. writeCommand("*v6W");
  511. writeBytes(new byte[]{0, 1, 1, 8, 8, 8});
  512. writeCommand(String.format("*v%da%db%dc0I", col.getRed(), col.getGreen(), col.getBlue()));
  513. writeCommand("*v0S");
  514. }
  515. /**
  516. * Select the current pattern
  517. * @param patternID the pattern ID (&lt;ESC&gt;*c#G command)
  518. * @param pattern the pattern type (&lt;ESC&gt;*v#T command)
  519. * @throws IOException In case of an I/O error
  520. */
  521. public void selectCurrentPattern(int patternID, int pattern) throws IOException {
  522. if (pattern > 1) {
  523. writeCommand("*c" + patternID + "G");
  524. }
  525. writeCommand("*v" + pattern + "T");
  526. }
  527. /**
  528. * Sets the dithering quality used when encoding gray or color images. If not explicitely
  529. * set a medium setting (0.5f) is used.
  530. * @param quality a quality setting between 0.0f (worst/fastest) and 1.0f (best/slowest)
  531. */
  532. public void setDitheringQuality(float quality) {
  533. quality = Math.min(Math.max(0f, quality), 1.0f);
  534. this.ditheringQuality = quality;
  535. }
  536. /**
  537. * Returns the dithering quality used when encoding gray or color images.
  538. * @return the quality setting between 0.0f (worst/fastest) and 1.0f (best/slowest)
  539. */
  540. public float getDitheringQuality() {
  541. return this.ditheringQuality;
  542. }
  543. /**
  544. * Indicates whether an image is a monochrome (b/w) image.
  545. * @param img the image
  546. * @return true if it's a monochrome image
  547. */
  548. public static boolean isMonochromeImage(RenderedImage img) {
  549. return BitmapImageUtil.isMonochromeImage(img);
  550. }
  551. /**
  552. * Indicates whether an image is a grayscale image.
  553. * @param img the image
  554. * @return true if it's a grayscale image
  555. */
  556. public static boolean isGrayscaleImage(RenderedImage img) {
  557. return BitmapImageUtil.isGrayscaleImage(img);
  558. }
  559. private static int jaiAvailable = -1; //no synchronization necessary, not critical
  560. /**
  561. * Indicates whether JAI is available. JAI has shown to be reliable when dithering a
  562. * grayscale or color image to monochrome bitmaps (1-bit).
  563. * @return true if JAI is available
  564. */
  565. public static boolean isJAIAvailable() {
  566. if (jaiAvailable < 0) {
  567. try {
  568. String clName = "javax.media.jai.JAI";
  569. Class.forName(clName);
  570. jaiAvailable = 1;
  571. } catch (ClassNotFoundException cnfe) {
  572. jaiAvailable = 0;
  573. }
  574. }
  575. return (jaiAvailable > 0);
  576. }
  577. private int calculatePCLResolution(int resolution) {
  578. return calculatePCLResolution(resolution, false);
  579. }
  580. /**
  581. * Calculates the ideal PCL resolution for a given resolution.
  582. * @param resolution the input resolution
  583. * @param increased true if you want to go to a higher resolution, for example if you
  584. * convert grayscale or color images to monochrome images so dithering has
  585. * a chance to generate better quality.
  586. * @return the resulting PCL resolution (one of 75, 100, 150, 200, 300, 600)
  587. */
  588. private int calculatePCLResolution(int resolution, boolean increased) {
  589. int choice = -1;
  590. for (int i = PCL_RESOLUTIONS.length - 2; i >= 0; i--) {
  591. if (resolution > PCL_RESOLUTIONS[i]) {
  592. int idx = i + 1;
  593. if (idx < PCL_RESOLUTIONS.length - 2) {
  594. idx += increased ? 2 : 0;
  595. } else if (idx < PCL_RESOLUTIONS.length - 1) {
  596. idx += increased ? 1 : 0;
  597. }
  598. choice = idx;
  599. break;
  600. //return PCL_RESOLUTIONS[idx];
  601. }
  602. }
  603. if (choice < 0) {
  604. choice = (increased ? 2 : 0);
  605. }
  606. while (choice > 0 && PCL_RESOLUTIONS[choice] > getMaximumBitmapResolution()) {
  607. choice--;
  608. }
  609. return PCL_RESOLUTIONS[choice];
  610. }
  611. private boolean isValidPCLResolution(int resolution) {
  612. return resolution == calculatePCLResolution(resolution);
  613. }
  614. //Threshold table to convert an alpha channel (8-bit) into a clip mask (1-bit)
  615. private static final byte[] THRESHOLD_TABLE = new byte[256];
  616. static { // Initialize the arrays
  617. for (int i = 0; i < 256; i++) {
  618. THRESHOLD_TABLE[i] = (byte) ((i < 240) ? 255 : 0);
  619. }
  620. }
  621. /* not used
  622. private RenderedImage getMask(RenderedImage img, Dimension targetDim) {
  623. ColorModel cm = img.getColorModel();
  624. if (cm.hasAlpha()) {
  625. BufferedImage alpha = new BufferedImage(img.getWidth(), img.getHeight(),
  626. BufferedImage.TYPE_BYTE_GRAY);
  627. Raster raster = img.getData();
  628. GraphicsUtil.copyBand(raster, cm.getNumColorComponents(), alpha.getRaster(), 0);
  629. BufferedImageOp op1 = new LookupOp(new ByteLookupTable(0, THRESHOLD_TABLE), null);
  630. BufferedImage alphat = op1.filter(alpha, null);
  631. BufferedImage mask;
  632. if (true) {
  633. mask = new BufferedImage(targetDim.width, targetDim.height,
  634. BufferedImage.TYPE_BYTE_BINARY);
  635. } else {
  636. byte[] arr = {(byte)0, (byte)0xff};
  637. ColorModel colorModel = new IndexColorModel(1, 2, arr, arr, arr);
  638. WritableRaster wraster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE,
  639. targetDim.width, targetDim.height, 1, 1, null);
  640. mask = new BufferedImage(colorModel, wraster, false, null);
  641. }
  642. Graphics2D g2d = mask.createGraphics();
  643. try {
  644. AffineTransform at = new AffineTransform();
  645. double sx = targetDim.getWidth() / img.getWidth();
  646. double sy = targetDim.getHeight() / img.getHeight();
  647. at.scale(sx, sy);
  648. g2d.drawRenderedImage(alphat, at);
  649. } finally {
  650. g2d.dispose();
  651. }
  652. return mask;
  653. } else {
  654. return null;
  655. }
  656. }
  657. */
  658. /**
  659. * Paint a bitmap at the current cursor position. The bitmap is converted to a monochrome
  660. * (1-bit) bitmap image.
  661. * @param img the bitmap image
  662. * @param targetDim the target Dimention (in mpt)
  663. * @param sourceTransparency true if the background should not be erased
  664. * @throws IOException In case of an I/O error
  665. */
  666. public void paintBitmap(RenderedImage img, Dimension targetDim, boolean sourceTransparency,
  667. PCLRenderingUtil pclUtil) throws IOException {
  668. final boolean printerSupportsColor = pclUtil.isColorEnabled();
  669. boolean monochrome = isMonochromeImage(img);
  670. double targetHResolution = img.getWidth() / UnitConv.mpt2in(targetDim.width);
  671. double targetVResolution = img.getHeight() / UnitConv.mpt2in(targetDim.height);
  672. double targetResolution = Math.max(targetHResolution, targetVResolution);
  673. int resolution = (int)Math.round(targetResolution);
  674. int effResolution = calculatePCLResolution(resolution, !(printerSupportsColor && !monochrome));
  675. Dimension orgDim = new Dimension(img.getWidth(), img.getHeight());
  676. Dimension effDim;
  677. if (targetResolution == effResolution) {
  678. effDim = orgDim; //avoid scaling side-effects
  679. } else {
  680. effDim = new Dimension(
  681. (int)Math.ceil(UnitConv.mpt2px(targetDim.width, effResolution)),
  682. (int)Math.ceil(UnitConv.mpt2px(targetDim.height, effResolution)));
  683. }
  684. boolean scaled = !orgDim.equals(effDim);
  685. if (!monochrome) {
  686. if (printerSupportsColor) {
  687. RenderedImage effImg = img;
  688. if (scaled) {
  689. effImg = BitmapImageUtil.convertTosRGB(img, effDim);
  690. }
  691. selectCurrentPattern(0, 0); //Solid black
  692. renderImageAsColor(effImg, effResolution);
  693. } else {
  694. //Transparency mask disabled. Doesn't work reliably
  695. /*
  696. final boolean transparencyDisabled = true;
  697. RenderedImage mask = (transparencyDisabled ? null : getMask(img, effDim));
  698. if (mask != null) {
  699. pushCursorPos();
  700. selectCurrentPattern(0, 1); //Solid white
  701. setTransparencyMode(true, true);
  702. paintMonochromeBitmap(mask, effResolution);
  703. popCursorPos();
  704. }
  705. */
  706. RenderedImage red = BitmapImageUtil.convertToMonochrome(
  707. img, effDim, this.ditheringQuality);
  708. selectCurrentPattern(0, 0); //Solid black
  709. setTransparencyMode(sourceTransparency /*|| mask != null*/, true);
  710. paintMonochromeBitmap(red, effResolution);
  711. }
  712. } else {
  713. RenderedImage effImg = img;
  714. if (scaled) {
  715. effImg = BitmapImageUtil.convertToMonochrome(img, effDim);
  716. }
  717. setSourceTransparencyMode(sourceTransparency);
  718. selectCurrentPattern(0, 0); //Solid black
  719. paintMonochromeBitmap(effImg, effResolution);
  720. }
  721. }
  722. private int toGray(int rgb) {
  723. // see http://www.jguru.com/faq/view.jsp?EID=221919
  724. double greyVal = 0.072169d * (rgb & 0xff);
  725. rgb >>= 8;
  726. greyVal += 0.715160d * (rgb & 0xff);
  727. rgb >>= 8;
  728. greyVal += 0.212671d * (rgb & 0xff);
  729. return (int)greyVal;
  730. }
  731. private void renderImageAsColor(RenderedImage imgOrg, int dpi) throws IOException {
  732. BufferedImage img = new BufferedImage(imgOrg.getWidth(), imgOrg.getHeight(), BufferedImage.TYPE_INT_RGB);
  733. Graphics2D g = img.createGraphics();
  734. g.setColor(Color.WHITE);
  735. g.fillRect(0, 0, imgOrg.getWidth(), imgOrg.getHeight());
  736. g.drawImage((Image) imgOrg, 0, 0, null);
  737. if (!isValidPCLResolution(dpi)) {
  738. throw new IllegalArgumentException("Invalid PCL resolution: " + dpi);
  739. }
  740. int w = img.getWidth();
  741. ColorModel cm = img.getColorModel();
  742. if (cm instanceof DirectColorModel) {
  743. writeCommand("*v6W"); // ImagingMode
  744. out.write(new byte[]{0, 3, 0, 8, 8, 8});
  745. } else {
  746. IndexColorModel icm = (IndexColorModel)cm;
  747. writeCommand("*v6W"); // ImagingMode
  748. out.write(new byte[]{0, 1, (byte)icm.getMapSize(), 8, 8, 8});
  749. byte[] reds = new byte[256];
  750. byte[] greens = new byte[256];
  751. byte[] blues = new byte[256];
  752. icm.getReds(reds);
  753. icm.getGreens(greens);
  754. icm.getBlues(blues);
  755. for (int i = 0; i < icm.getMapSize(); i++) {
  756. writeCommand("*v" + (reds[i] & 0xFF) + "A"); //ColorComponentOne
  757. writeCommand("*v" + (greens[i] & 0xFF) + "B"); //ColorComponentTwo
  758. writeCommand("*v" + (blues[i] & 0xFF) + "C"); //ColorComponentThree
  759. writeCommand("*v" + i + "I"); //AssignColorIndex
  760. }
  761. }
  762. setRasterGraphicsResolution(dpi);
  763. writeCommand("*r0f" + img.getHeight() + "t" + (w) + "S");
  764. writeCommand("*r1A");
  765. Raster raster = img.getData();
  766. ColorEncoder encoder = new ColorEncoder(img);
  767. // Transfer graphics data
  768. if (cm.getTransferType() == DataBuffer.TYPE_BYTE) {
  769. DataBufferByte dataBuffer = (DataBufferByte)raster.getDataBuffer();
  770. if (img.getSampleModel() instanceof MultiPixelPackedSampleModel && dataBuffer.getNumBanks() == 1) {
  771. byte[] buf = dataBuffer.getData();
  772. MultiPixelPackedSampleModel sampleModel = (MultiPixelPackedSampleModel)img.getSampleModel();
  773. int scanlineStride = sampleModel.getScanlineStride();
  774. int idx = 0;
  775. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  776. for (int x = 0; x < scanlineStride; x++) {
  777. encoder.add8Bits(buf[idx]);
  778. idx++;
  779. }
  780. encoder.endLine();
  781. }
  782. } else {
  783. throw new IOException("Unsupported image");
  784. }
  785. } else if (cm.getTransferType() == DataBuffer.TYPE_INT) {
  786. DataBufferInt dataBuffer = (DataBufferInt)raster.getDataBuffer();
  787. if (img.getSampleModel() instanceof SinglePixelPackedSampleModel && dataBuffer.getNumBanks() == 1) {
  788. int[] buf = dataBuffer.getData();
  789. SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel)img.getSampleModel();
  790. int scanlineStride = sampleModel.getScanlineStride();
  791. int idx = 0;
  792. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  793. for (int x = 0; x < scanlineStride; x++) {
  794. encoder.add8Bits((byte)(buf[idx] >> 16));
  795. encoder.add8Bits((byte)(buf[idx] >> 8));
  796. encoder.add8Bits((byte)(buf[idx] >> 0));
  797. idx++;
  798. }
  799. encoder.endLine();
  800. }
  801. } else {
  802. throw new IOException("Unsupported image");
  803. }
  804. } else {
  805. throw new IOException("Unsupported image");
  806. }
  807. // End raster graphics
  808. writeCommand("*rB");
  809. }
  810. /**
  811. * Paint a bitmap at the current cursor position. The bitmap must be a monochrome
  812. * (1-bit) bitmap image.
  813. * @param img the bitmap image (must be 1-bit b/w)
  814. * @param resolution the resolution of the image (must be a PCL resolution)
  815. * @throws IOException In case of an I/O error
  816. */
  817. public void paintMonochromeBitmap(RenderedImage img, int resolution) throws IOException {
  818. if (!isValidPCLResolution(resolution)) {
  819. throw new IllegalArgumentException("Invalid PCL resolution: " + resolution);
  820. }
  821. boolean monochrome = isMonochromeImage(img);
  822. if (!monochrome) {
  823. throw new IllegalArgumentException("img must be a monochrome image");
  824. }
  825. setRasterGraphicsResolution(resolution);
  826. writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A");
  827. Raster raster = img.getData();
  828. Encoder encoder = new Encoder(img);
  829. // Transfer graphics data
  830. int imgw = img.getWidth();
  831. IndexColorModel cm = (IndexColorModel)img.getColorModel();
  832. if (cm.getTransferType() == DataBuffer.TYPE_BYTE) {
  833. DataBufferByte dataBuffer = (DataBufferByte)raster.getDataBuffer();
  834. MultiPixelPackedSampleModel packedSampleModel = new MultiPixelPackedSampleModel(
  835. DataBuffer.TYPE_BYTE, img.getWidth(), img.getHeight(), 1);
  836. if (img.getSampleModel().equals(packedSampleModel)
  837. && dataBuffer.getNumBanks() == 1) {
  838. //Optimized packed encoding
  839. byte[] buf = dataBuffer.getData();
  840. int scanlineStride = packedSampleModel.getScanlineStride();
  841. int idx = 0;
  842. int c0 = toGray(cm.getRGB(0));
  843. int c1 = toGray(cm.getRGB(1));
  844. boolean zeroIsWhite = c0 > c1;
  845. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  846. for (int x = 0, maxx = scanlineStride; x < maxx; x++) {
  847. if (zeroIsWhite) {
  848. encoder.add8Bits(buf[idx]);
  849. } else {
  850. encoder.add8Bits((byte)~buf[idx]);
  851. }
  852. idx++;
  853. }
  854. encoder.endLine();
  855. }
  856. } else {
  857. //Optimized non-packed encoding
  858. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  859. byte[] line = (byte[])raster.getDataElements(0, y, imgw, 1, null);
  860. for (int x = 0, maxx = imgw; x < maxx; x++) {
  861. encoder.addBit(line[x] == 0);
  862. }
  863. encoder.endLine();
  864. }
  865. }
  866. } else {
  867. //Safe but slow fallback
  868. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  869. for (int x = 0, maxx = imgw; x < maxx; x++) {
  870. int sample = raster.getSample(x, y, 0);
  871. encoder.addBit(sample == 0);
  872. }
  873. encoder.endLine();
  874. }
  875. }
  876. // End raster graphics
  877. writeCommand("*rB");
  878. }
  879. private class Encoder {
  880. private int imgw;
  881. private int bytewidth;
  882. private byte[] rle; //compressed (RLE)
  883. private byte[] uncompressed; //uncompressed
  884. private int lastcount = -1;
  885. private byte lastbyte;
  886. private int rlewidth;
  887. private byte ib; //current image bits
  888. private int x;
  889. private boolean zeroRow = true;
  890. public Encoder(RenderedImage img) {
  891. imgw = img.getWidth();
  892. bytewidth = (imgw / 8);
  893. if ((imgw % 8) != 0) {
  894. bytewidth++;
  895. }
  896. rle = new byte[bytewidth * 2];
  897. uncompressed = new byte[bytewidth];
  898. }
  899. public void addBit(boolean bit) {
  900. //Set image bit for black
  901. if (bit) {
  902. ib |= 1;
  903. }
  904. //RLE encoding
  905. if ((x % 8) == 7 || ((x + 1) == imgw)) {
  906. finishedByte();
  907. } else {
  908. ib <<= 1;
  909. }
  910. x++;
  911. }
  912. public void add8Bits(byte b) {
  913. ib = b;
  914. finishedByte();
  915. x += 8;
  916. }
  917. private void finishedByte() {
  918. if (rlewidth < bytewidth) {
  919. if (lastcount >= 0) {
  920. if (ib == lastbyte) {
  921. lastcount++;
  922. } else {
  923. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  924. rle[rlewidth++] = lastbyte;
  925. lastbyte = ib;
  926. lastcount = 0;
  927. }
  928. } else {
  929. lastbyte = ib;
  930. lastcount = 0;
  931. }
  932. if (lastcount == 255 || ((x + 1) == imgw)) {
  933. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  934. rle[rlewidth++] = lastbyte;
  935. lastbyte = 0;
  936. lastcount = -1;
  937. }
  938. }
  939. uncompressed[x / 8] = ib;
  940. if (ib != 0) {
  941. zeroRow = false;
  942. }
  943. ib = 0;
  944. }
  945. public void endLine() throws IOException {
  946. if (zeroRow && PCLGenerator.this.currentSourceTransparency) {
  947. writeCommand("*b1Y");
  948. } else if (rlewidth < bytewidth) {
  949. writeCommand("*b1m" + rlewidth + "W");
  950. out.write(rle, 0, rlewidth);
  951. } else {
  952. writeCommand("*b0m" + bytewidth + "W");
  953. out.write(uncompressed);
  954. }
  955. lastcount = -1;
  956. rlewidth = 0;
  957. ib = 0;
  958. x = 0;
  959. zeroRow = true;
  960. }
  961. }
  962. private class ColorEncoder {
  963. private int imgw;
  964. private int bytewidth;
  965. private byte ib; //current image bits
  966. private int currentIndex;
  967. private int len;
  968. private int shiftBit = 0x80;
  969. private int whiteLines;
  970. final byte[] zeros;
  971. final byte[] buff1;
  972. final byte[] buff2;
  973. final byte[] encodedRun;
  974. final byte[] encodedTagged;
  975. final byte[] encodedDelta;
  976. byte[] seed;
  977. byte[] current;
  978. int compression;
  979. int seedLen;
  980. public ColorEncoder(RenderedImage img) {
  981. imgw = img.getWidth();
  982. bytewidth = imgw * 3 + 1;
  983. zeros = new byte[bytewidth];
  984. buff1 = new byte[bytewidth];
  985. buff2 = new byte[bytewidth];
  986. encodedRun = new byte[bytewidth];
  987. encodedTagged = new byte[bytewidth];
  988. encodedDelta = new byte[bytewidth];
  989. seed = buff1;
  990. current = buff2;
  991. seedLen = 0;
  992. compression = (-1);
  993. System.arraycopy(zeros, 0, seed, 0, zeros.length);
  994. }
  995. private int runCompression(byte[] buff, int len) {
  996. int bytes = 0;
  997. try {
  998. for (int i = 0; i < len;) {
  999. int sameCount;
  1000. byte seed = current[i++];
  1001. for (sameCount = 1; i < len && current[i] == seed; i++) {
  1002. sameCount++;
  1003. }
  1004. for (; sameCount > 256; sameCount -= 256) {
  1005. buff[bytes++] = (byte)255;
  1006. buff[bytes++] = seed;
  1007. }
  1008. if (sameCount > 0) {
  1009. buff[bytes++] = (byte)(sameCount - 1);
  1010. buff[bytes++] = seed;
  1011. }
  1012. }
  1013. } catch (ArrayIndexOutOfBoundsException e) {
  1014. return len + 1;
  1015. }
  1016. return bytes;
  1017. }
  1018. private int deltaCompression(byte[] seed, byte[] buff, int len) {
  1019. int bytes = 0;
  1020. try {
  1021. for (int i = 0; i < len;) {
  1022. int sameCount;
  1023. int diffCount;
  1024. for (sameCount = 0; i < len && current[i] == seed[i]; i++) {
  1025. sameCount++;
  1026. }
  1027. for (diffCount = 0; i < len && current[i] != seed[i]; i++) {
  1028. diffCount++;
  1029. }
  1030. for (; diffCount != 0;) {
  1031. int diffToWrite = (diffCount > 8) ? 8 : diffCount;
  1032. int sameToWrite = (sameCount > 31) ? 31 : sameCount;
  1033. buff[bytes++] = (byte)(((diffToWrite - 1) << 5) | sameToWrite);
  1034. sameCount -= sameToWrite;
  1035. if (sameToWrite == 31) {
  1036. for (; sameCount >= 255; sameCount -= 255) {
  1037. buff[bytes++] = (byte)255;
  1038. }
  1039. buff[bytes++] = (byte)sameCount;
  1040. sameCount = 0;
  1041. }
  1042. System.arraycopy(current, i - diffCount, buff, bytes, diffToWrite);
  1043. bytes += diffToWrite;
  1044. diffCount -= diffToWrite;
  1045. }
  1046. }
  1047. } catch (ArrayIndexOutOfBoundsException e) {
  1048. return len + 1;
  1049. }
  1050. return bytes;
  1051. }
  1052. private int tiffCompression(byte[] encodedTagged, int len) {
  1053. int literalCount = 0;
  1054. int bytes = 0;
  1055. try {
  1056. for (int from = 0; from < len;) {
  1057. int repeatLength;
  1058. int repeatValue = current[from];
  1059. for (repeatLength = 1; repeatLength < 128
  1060. && from + repeatLength < len
  1061. && current[from + repeatLength] == repeatValue;) {
  1062. repeatLength++;
  1063. }
  1064. if (literalCount == 128 || (repeatLength > 2 && literalCount > 0)) {
  1065. encodedTagged[bytes++] = (byte)(literalCount - 1);
  1066. System.arraycopy(current, from - literalCount, encodedTagged, bytes, literalCount);
  1067. bytes += literalCount;
  1068. literalCount = 0;
  1069. }
  1070. if (repeatLength > 2) {
  1071. encodedTagged[bytes++] = (byte)(1 - repeatLength);
  1072. encodedTagged[bytes++] = current[from];
  1073. from += repeatLength;
  1074. } else {
  1075. literalCount++;
  1076. from++;
  1077. }
  1078. }
  1079. if (literalCount > 0) {
  1080. encodedTagged[bytes++] = (byte)(literalCount - 1);
  1081. System.arraycopy(current, (3 * len) - literalCount, encodedTagged, bytes, literalCount);
  1082. bytes += literalCount;
  1083. }
  1084. } catch (ArrayIndexOutOfBoundsException e) {
  1085. return len + 1;
  1086. }
  1087. return bytes;
  1088. }
  1089. public void addBit(boolean bit) {
  1090. //Set image bit for black
  1091. if (bit) {
  1092. ib |= shiftBit;
  1093. }
  1094. shiftBit >>= 1;
  1095. if (shiftBit == 0) {
  1096. add8Bits(ib);
  1097. shiftBit = 0x80;
  1098. ib = 0;
  1099. }
  1100. }
  1101. public void add8Bits(byte b) {
  1102. current[currentIndex++] = b;
  1103. if (b != 0) {
  1104. len = currentIndex;
  1105. }
  1106. }
  1107. public void endLine() throws IOException {
  1108. if (len == 0) {
  1109. whiteLines++;
  1110. } else {
  1111. if (whiteLines > 0) {
  1112. writeCommand("*b" + whiteLines + "Y");
  1113. whiteLines = 0;
  1114. }
  1115. int unencodedCount = len;
  1116. int runCount = runCompression(encodedRun, len);
  1117. int tiffCount = tiffCompression(encodedTagged, len);
  1118. int deltaCount = deltaCompression(seed, encodedDelta, Math.max(len, seedLen));
  1119. int bestCount = Math.min(unencodedCount, Math.min(runCount, Math.min(tiffCount, deltaCount)));
  1120. int bestCompression;
  1121. if (bestCount == unencodedCount) {
  1122. bestCompression = 0;
  1123. } else if (bestCount == runCount) {
  1124. bestCompression = 1;
  1125. } else if (bestCount == tiffCount) {
  1126. bestCompression = 2;
  1127. } else {
  1128. bestCompression = 3;
  1129. }
  1130. if (compression != bestCompression) {
  1131. compression = bestCompression;
  1132. writeCommand("*b" + compression + "M");
  1133. }
  1134. if (bestCompression == 0) {
  1135. writeCommand("*b" + unencodedCount + "W");
  1136. out.write(current, 0, unencodedCount);
  1137. } else if (bestCompression == 1) {
  1138. writeCommand("*b" + runCount + "W");
  1139. out.write(encodedRun, 0, runCount);
  1140. } else if (bestCompression == 2) {
  1141. writeCommand("*b" + tiffCount + "W");
  1142. out.write(encodedTagged, 0, tiffCount);
  1143. } else if (bestCompression == 3) {
  1144. writeCommand("*b" + deltaCount + "W");
  1145. out.write(encodedDelta, 0, deltaCount);
  1146. }
  1147. if (current == buff1) {
  1148. seed = buff1;
  1149. current = buff2;
  1150. } else {
  1151. seed = buff2;
  1152. current = buff1;
  1153. }
  1154. seedLen = len;
  1155. }
  1156. shiftBit = 0x80;
  1157. ib = 0;
  1158. len = 0;
  1159. currentIndex = 0;
  1160. }
  1161. }
  1162. }