選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

PCLGenerator.java 47KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286
  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. selectCurrentPattern(0, 0); //Solid black
  688. renderImageAsColor(img, effResolution);
  689. } else {
  690. //Transparency mask disabled. Doesn't work reliably
  691. /*
  692. final boolean transparencyDisabled = true;
  693. RenderedImage mask = (transparencyDisabled ? null : getMask(img, effDim));
  694. if (mask != null) {
  695. pushCursorPos();
  696. selectCurrentPattern(0, 1); //Solid white
  697. setTransparencyMode(true, true);
  698. paintMonochromeBitmap(mask, effResolution);
  699. popCursorPos();
  700. }
  701. */
  702. RenderedImage red = BitmapImageUtil.convertToMonochrome(
  703. img, effDim, this.ditheringQuality);
  704. selectCurrentPattern(0, 0); //Solid black
  705. setTransparencyMode(sourceTransparency /*|| mask != null*/, true);
  706. paintMonochromeBitmap(red, effResolution);
  707. }
  708. } else {
  709. RenderedImage effImg = img;
  710. if (scaled) {
  711. effImg = BitmapImageUtil.convertToMonochrome(img, effDim);
  712. }
  713. setSourceTransparencyMode(sourceTransparency);
  714. selectCurrentPattern(0, 0); //Solid black
  715. paintMonochromeBitmap(effImg, effResolution);
  716. }
  717. }
  718. private int toGray(int rgb) {
  719. // see http://www.jguru.com/faq/view.jsp?EID=221919
  720. double greyVal = 0.072169d * (rgb & 0xff);
  721. rgb >>= 8;
  722. greyVal += 0.715160d * (rgb & 0xff);
  723. rgb >>= 8;
  724. greyVal += 0.212671d * (rgb & 0xff);
  725. return (int)greyVal;
  726. }
  727. private void renderImageAsColor(RenderedImage imgOrg, int dpi) throws IOException {
  728. BufferedImage img = new BufferedImage(imgOrg.getWidth(), imgOrg.getHeight(), BufferedImage.TYPE_INT_RGB);
  729. Graphics2D g = img.createGraphics();
  730. g.setColor(Color.WHITE);
  731. g.fillRect(0, 0, imgOrg.getWidth(), imgOrg.getHeight());
  732. g.drawImage((Image) imgOrg, 0, 0, null);
  733. if (!isValidPCLResolution(dpi)) {
  734. throw new IllegalArgumentException("Invalid PCL resolution: " + dpi);
  735. }
  736. int w = img.getWidth();
  737. ColorModel cm = img.getColorModel();
  738. if (cm instanceof DirectColorModel) {
  739. writeCommand("*v6W"); // ImagingMode
  740. out.write(new byte[]{0, 3, 0, 8, 8, 8});
  741. } else {
  742. IndexColorModel icm = (IndexColorModel)cm;
  743. writeCommand("*v6W"); // ImagingMode
  744. out.write(new byte[]{0, 1, (byte)icm.getMapSize(), 8, 8, 8});
  745. byte[] reds = new byte[256];
  746. byte[] greens = new byte[256];
  747. byte[] blues = new byte[256];
  748. icm.getReds(reds);
  749. icm.getGreens(greens);
  750. icm.getBlues(blues);
  751. for (int i = 0; i < icm.getMapSize(); i++) {
  752. writeCommand("*v" + (reds[i] & 0xFF) + "A"); //ColorComponentOne
  753. writeCommand("*v" + (greens[i] & 0xFF) + "B"); //ColorComponentTwo
  754. writeCommand("*v" + (blues[i] & 0xFF) + "C"); //ColorComponentThree
  755. writeCommand("*v" + i + "I"); //AssignColorIndex
  756. }
  757. }
  758. setRasterGraphicsResolution(dpi);
  759. writeCommand("*r0f" + img.getHeight() + "t" + (w) + "S");
  760. writeCommand("*r1A");
  761. Raster raster = img.getData();
  762. ColorEncoder encoder = new ColorEncoder(img);
  763. // Transfer graphics data
  764. if (cm.getTransferType() == DataBuffer.TYPE_BYTE) {
  765. DataBufferByte dataBuffer = (DataBufferByte)raster.getDataBuffer();
  766. if (img.getSampleModel() instanceof MultiPixelPackedSampleModel && dataBuffer.getNumBanks() == 1) {
  767. byte[] buf = dataBuffer.getData();
  768. MultiPixelPackedSampleModel sampleModel = (MultiPixelPackedSampleModel)img.getSampleModel();
  769. int scanlineStride = sampleModel.getScanlineStride();
  770. int idx = 0;
  771. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  772. for (int x = 0; x < scanlineStride; x++) {
  773. encoder.add8Bits(buf[idx]);
  774. idx++;
  775. }
  776. encoder.endLine();
  777. }
  778. } else {
  779. throw new IOException("Unsupported image");
  780. }
  781. } else if (cm.getTransferType() == DataBuffer.TYPE_INT) {
  782. DataBufferInt dataBuffer = (DataBufferInt)raster.getDataBuffer();
  783. if (img.getSampleModel() instanceof SinglePixelPackedSampleModel && dataBuffer.getNumBanks() == 1) {
  784. int[] buf = dataBuffer.getData();
  785. SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel)img.getSampleModel();
  786. int scanlineStride = sampleModel.getScanlineStride();
  787. int idx = 0;
  788. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  789. for (int x = 0; x < scanlineStride; x++) {
  790. encoder.add8Bits((byte)(buf[idx] >> 16));
  791. encoder.add8Bits((byte)(buf[idx] >> 8));
  792. encoder.add8Bits((byte)(buf[idx] >> 0));
  793. idx++;
  794. }
  795. encoder.endLine();
  796. }
  797. } else {
  798. throw new IOException("Unsupported image");
  799. }
  800. } else {
  801. throw new IOException("Unsupported image");
  802. }
  803. // End raster graphics
  804. writeCommand("*rB");
  805. }
  806. /**
  807. * Paint a bitmap at the current cursor position. The bitmap must be a monochrome
  808. * (1-bit) bitmap image.
  809. * @param img the bitmap image (must be 1-bit b/w)
  810. * @param resolution the resolution of the image (must be a PCL resolution)
  811. * @throws IOException In case of an I/O error
  812. */
  813. public void paintMonochromeBitmap(RenderedImage img, int resolution) throws IOException {
  814. if (!isValidPCLResolution(resolution)) {
  815. throw new IllegalArgumentException("Invalid PCL resolution: " + resolution);
  816. }
  817. boolean monochrome = isMonochromeImage(img);
  818. if (!monochrome) {
  819. throw new IllegalArgumentException("img must be a monochrome image");
  820. }
  821. setRasterGraphicsResolution(resolution);
  822. writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A");
  823. Raster raster = img.getData();
  824. Encoder encoder = new Encoder(img);
  825. // Transfer graphics data
  826. int imgw = img.getWidth();
  827. IndexColorModel cm = (IndexColorModel)img.getColorModel();
  828. if (cm.getTransferType() == DataBuffer.TYPE_BYTE) {
  829. DataBufferByte dataBuffer = (DataBufferByte)raster.getDataBuffer();
  830. MultiPixelPackedSampleModel packedSampleModel = new MultiPixelPackedSampleModel(
  831. DataBuffer.TYPE_BYTE, img.getWidth(), img.getHeight(), 1);
  832. if (img.getSampleModel().equals(packedSampleModel)
  833. && dataBuffer.getNumBanks() == 1) {
  834. //Optimized packed encoding
  835. byte[] buf = dataBuffer.getData();
  836. int scanlineStride = packedSampleModel.getScanlineStride();
  837. int idx = 0;
  838. int c0 = toGray(cm.getRGB(0));
  839. int c1 = toGray(cm.getRGB(1));
  840. boolean zeroIsWhite = c0 > c1;
  841. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  842. for (int x = 0, maxx = scanlineStride; x < maxx; x++) {
  843. if (zeroIsWhite) {
  844. encoder.add8Bits(buf[idx]);
  845. } else {
  846. encoder.add8Bits((byte)~buf[idx]);
  847. }
  848. idx++;
  849. }
  850. encoder.endLine();
  851. }
  852. } else {
  853. //Optimized non-packed encoding
  854. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  855. byte[] line = (byte[])raster.getDataElements(0, y, imgw, 1, null);
  856. for (int x = 0, maxx = imgw; x < maxx; x++) {
  857. encoder.addBit(line[x] == 0);
  858. }
  859. encoder.endLine();
  860. }
  861. }
  862. } else {
  863. //Safe but slow fallback
  864. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  865. for (int x = 0, maxx = imgw; x < maxx; x++) {
  866. int sample = raster.getSample(x, y, 0);
  867. encoder.addBit(sample == 0);
  868. }
  869. encoder.endLine();
  870. }
  871. }
  872. // End raster graphics
  873. writeCommand("*rB");
  874. }
  875. private class Encoder {
  876. private int imgw;
  877. private int bytewidth;
  878. private byte[] rle; //compressed (RLE)
  879. private byte[] uncompressed; //uncompressed
  880. private int lastcount = -1;
  881. private byte lastbyte;
  882. private int rlewidth;
  883. private byte ib; //current image bits
  884. private int x;
  885. private boolean zeroRow = true;
  886. public Encoder(RenderedImage img) {
  887. imgw = img.getWidth();
  888. bytewidth = (imgw / 8);
  889. if ((imgw % 8) != 0) {
  890. bytewidth++;
  891. }
  892. rle = new byte[bytewidth * 2];
  893. uncompressed = new byte[bytewidth];
  894. }
  895. public void addBit(boolean bit) {
  896. //Set image bit for black
  897. if (bit) {
  898. ib |= 1;
  899. }
  900. //RLE encoding
  901. if ((x % 8) == 7 || ((x + 1) == imgw)) {
  902. finishedByte();
  903. } else {
  904. ib <<= 1;
  905. }
  906. x++;
  907. }
  908. public void add8Bits(byte b) {
  909. ib = b;
  910. finishedByte();
  911. x += 8;
  912. }
  913. private void finishedByte() {
  914. if (rlewidth < bytewidth) {
  915. if (lastcount >= 0) {
  916. if (ib == lastbyte) {
  917. lastcount++;
  918. } else {
  919. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  920. rle[rlewidth++] = lastbyte;
  921. lastbyte = ib;
  922. lastcount = 0;
  923. }
  924. } else {
  925. lastbyte = ib;
  926. lastcount = 0;
  927. }
  928. if (lastcount == 255 || ((x + 1) == imgw)) {
  929. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  930. rle[rlewidth++] = lastbyte;
  931. lastbyte = 0;
  932. lastcount = -1;
  933. }
  934. }
  935. uncompressed[x / 8] = ib;
  936. if (ib != 0) {
  937. zeroRow = false;
  938. }
  939. ib = 0;
  940. }
  941. public void endLine() throws IOException {
  942. if (zeroRow && PCLGenerator.this.currentSourceTransparency) {
  943. writeCommand("*b1Y");
  944. } else if (rlewidth < bytewidth) {
  945. writeCommand("*b1m" + rlewidth + "W");
  946. out.write(rle, 0, rlewidth);
  947. } else {
  948. writeCommand("*b0m" + bytewidth + "W");
  949. out.write(uncompressed);
  950. }
  951. lastcount = -1;
  952. rlewidth = 0;
  953. ib = 0;
  954. x = 0;
  955. zeroRow = true;
  956. }
  957. }
  958. private class ColorEncoder {
  959. private int imgw;
  960. private int bytewidth;
  961. private byte ib; //current image bits
  962. private int currentIndex;
  963. private int len;
  964. private int shiftBit = 0x80;
  965. private int whiteLines;
  966. final byte[] zeros;
  967. final byte[] buff1;
  968. final byte[] buff2;
  969. final byte[] encodedRun;
  970. final byte[] encodedTagged;
  971. final byte[] encodedDelta;
  972. byte[] seed;
  973. byte[] current;
  974. int compression;
  975. int seedLen;
  976. public ColorEncoder(RenderedImage img) {
  977. imgw = img.getWidth();
  978. bytewidth = imgw * 3 + 1;
  979. zeros = new byte[bytewidth];
  980. buff1 = new byte[bytewidth];
  981. buff2 = new byte[bytewidth];
  982. encodedRun = new byte[bytewidth];
  983. encodedTagged = new byte[bytewidth];
  984. encodedDelta = new byte[bytewidth];
  985. seed = buff1;
  986. current = buff2;
  987. seedLen = 0;
  988. compression = (-1);
  989. System.arraycopy(zeros, 0, seed, 0, zeros.length);
  990. }
  991. private int runCompression(byte[] buff, int len) {
  992. int bytes = 0;
  993. try {
  994. for (int i = 0; i < len;) {
  995. int sameCount;
  996. byte seed = current[i++];
  997. for (sameCount = 1; i < len && current[i] == seed; i++) {
  998. sameCount++;
  999. }
  1000. for (; sameCount > 256; sameCount -= 256) {
  1001. buff[bytes++] = (byte)255;
  1002. buff[bytes++] = seed;
  1003. }
  1004. if (sameCount > 0) {
  1005. buff[bytes++] = (byte)(sameCount - 1);
  1006. buff[bytes++] = seed;
  1007. }
  1008. }
  1009. } catch (ArrayIndexOutOfBoundsException e) {
  1010. return len + 1;
  1011. }
  1012. return bytes;
  1013. }
  1014. private int deltaCompression(byte[] seed, byte[] buff, int len) {
  1015. int bytes = 0;
  1016. try {
  1017. for (int i = 0; i < len;) {
  1018. int sameCount;
  1019. int diffCount;
  1020. for (sameCount = 0; i < len && current[i] == seed[i]; i++) {
  1021. sameCount++;
  1022. }
  1023. for (diffCount = 0; i < len && current[i] != seed[i]; i++) {
  1024. diffCount++;
  1025. }
  1026. for (; diffCount != 0;) {
  1027. int diffToWrite = (diffCount > 8) ? 8 : diffCount;
  1028. int sameToWrite = (sameCount > 31) ? 31 : sameCount;
  1029. buff[bytes++] = (byte)(((diffToWrite - 1) << 5) | sameToWrite);
  1030. sameCount -= sameToWrite;
  1031. if (sameToWrite == 31) {
  1032. for (; sameCount >= 255; sameCount -= 255) {
  1033. buff[bytes++] = (byte)255;
  1034. }
  1035. buff[bytes++] = (byte)sameCount;
  1036. sameCount = 0;
  1037. }
  1038. System.arraycopy(current, i - diffCount, buff, bytes, diffToWrite);
  1039. bytes += diffToWrite;
  1040. diffCount -= diffToWrite;
  1041. }
  1042. }
  1043. } catch (ArrayIndexOutOfBoundsException e) {
  1044. return len + 1;
  1045. }
  1046. return bytes;
  1047. }
  1048. private int tiffCompression(byte[] encodedTagged, int len) {
  1049. int literalCount = 0;
  1050. int bytes = 0;
  1051. try {
  1052. for (int from = 0; from < len;) {
  1053. int repeatLength;
  1054. int repeatValue = current[from];
  1055. for (repeatLength = 1; repeatLength < 128
  1056. && from + repeatLength < len
  1057. && current[from + repeatLength] == repeatValue;) {
  1058. repeatLength++;
  1059. }
  1060. if (literalCount == 128 || (repeatLength > 2 && literalCount > 0)) {
  1061. encodedTagged[bytes++] = (byte)(literalCount - 1);
  1062. System.arraycopy(current, from - literalCount, encodedTagged, bytes, literalCount);
  1063. bytes += literalCount;
  1064. literalCount = 0;
  1065. }
  1066. if (repeatLength > 2) {
  1067. encodedTagged[bytes++] = (byte)(1 - repeatLength);
  1068. encodedTagged[bytes++] = current[from];
  1069. from += repeatLength;
  1070. } else {
  1071. literalCount++;
  1072. from++;
  1073. }
  1074. }
  1075. if (literalCount > 0) {
  1076. encodedTagged[bytes++] = (byte)(literalCount - 1);
  1077. System.arraycopy(current, (3 * len) - literalCount, encodedTagged, bytes, literalCount);
  1078. bytes += literalCount;
  1079. }
  1080. } catch (ArrayIndexOutOfBoundsException e) {
  1081. return len + 1;
  1082. }
  1083. return bytes;
  1084. }
  1085. public void addBit(boolean bit) {
  1086. //Set image bit for black
  1087. if (bit) {
  1088. ib |= shiftBit;
  1089. }
  1090. shiftBit >>= 1;
  1091. if (shiftBit == 0) {
  1092. add8Bits(ib);
  1093. shiftBit = 0x80;
  1094. ib = 0;
  1095. }
  1096. }
  1097. public void add8Bits(byte b) {
  1098. current[currentIndex++] = b;
  1099. if (b != 0) {
  1100. len = currentIndex;
  1101. }
  1102. }
  1103. public void endLine() throws IOException {
  1104. if (len == 0) {
  1105. whiteLines++;
  1106. } else {
  1107. if (whiteLines > 0) {
  1108. writeCommand("*b" + whiteLines + "Y");
  1109. whiteLines = 0;
  1110. }
  1111. int unencodedCount = len;
  1112. int runCount = runCompression(encodedRun, len);
  1113. int tiffCount = tiffCompression(encodedTagged, len);
  1114. int deltaCount = deltaCompression(seed, encodedDelta, Math.max(len, seedLen));
  1115. int bestCount = Math.min(unencodedCount, Math.min(runCount, Math.min(tiffCount, deltaCount)));
  1116. int bestCompression;
  1117. if (bestCount == unencodedCount) {
  1118. bestCompression = 0;
  1119. } else if (bestCount == runCount) {
  1120. bestCompression = 1;
  1121. } else if (bestCount == tiffCount) {
  1122. bestCompression = 2;
  1123. } else {
  1124. bestCompression = 3;
  1125. }
  1126. if (compression != bestCompression) {
  1127. compression = bestCompression;
  1128. writeCommand("*b" + compression + "M");
  1129. }
  1130. if (bestCompression == 0) {
  1131. writeCommand("*b" + unencodedCount + "W");
  1132. out.write(current, 0, unencodedCount);
  1133. } else if (bestCompression == 1) {
  1134. writeCommand("*b" + runCount + "W");
  1135. out.write(encodedRun, 0, runCount);
  1136. } else if (bestCompression == 2) {
  1137. writeCommand("*b" + tiffCount + "W");
  1138. out.write(encodedTagged, 0, tiffCount);
  1139. } else if (bestCompression == 3) {
  1140. writeCommand("*b" + deltaCount + "W");
  1141. out.write(encodedDelta, 0, deltaCount);
  1142. }
  1143. if (current == buff1) {
  1144. seed = buff1;
  1145. current = buff2;
  1146. } else {
  1147. seed = buff2;
  1148. current = buff1;
  1149. }
  1150. seedLen = len;
  1151. }
  1152. shiftBit = 0x80;
  1153. ib = 0;
  1154. len = 0;
  1155. currentIndex = 0;
  1156. }
  1157. }
  1158. }