diff options
author | Jeremias Maerki <jeremias@apache.org> | 2006-07-24 13:16:23 +0000 |
---|---|---|
committer | Jeremias Maerki <jeremias@apache.org> | 2006-07-24 13:16:23 +0000 |
commit | fe9c40f495aee9919b86a746dfd587069f7cc3f3 (patch) | |
tree | 140ec1ebac55584dc3e5917b38856f0862c9c419 /src/java/org/apache/fop/render/pcl/PCLGenerator.java | |
parent | 423453976de29088ee76156baec1a83bc58b9518 (diff) | |
download | xmlgraphics-fop-fe9c40f495aee9919b86a746dfd587069f7cc3f3.tar.gz xmlgraphics-fop-fe9c40f495aee9919b86a746dfd587069f7cc3f3.zip |
Promoting the PCL renderer from sandbox to the main source tree.
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@425038 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/render/pcl/PCLGenerator.java')
-rw-r--r-- | src/java/org/apache/fop/render/pcl/PCLGenerator.java | 751 |
1 files changed, 751 insertions, 0 deletions
diff --git a/src/java/org/apache/fop/render/pcl/PCLGenerator.java b/src/java/org/apache/fop/render/pcl/PCLGenerator.java new file mode 100644 index 000000000..8612a51e6 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/PCLGenerator.java @@ -0,0 +1,751 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.color.ColorSpace; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ByteLookupTable; +import java.awt.image.ColorConvertOp; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; +import java.awt.image.LookupOp; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.io.OutputStream; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +import org.apache.fop.util.UnitConv; +import org.apache.xmlgraphics.image.GraphicsUtil; + +/** + * This class provides methods for generating PCL print files. + */ +public class PCLGenerator { + + /** The ESC (escape) character */ + public static final char ESC = '\033'; + + /** A list of all supported resolutions in PCL (values in dpi) */ + public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600}; + + private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); + private final DecimalFormat df2 = new DecimalFormat("0.##", symbols); + private final DecimalFormat df4 = new DecimalFormat("0.####", symbols); + + private OutputStream out; + + private boolean currentSourceTransparency = true; + private boolean currentPatternTransparency = true; + + private int maxBitmapResolution = PCL_RESOLUTIONS[PCL_RESOLUTIONS.length - 1]; + + /** + * Main constructor. + * @param out the OutputStream to write the PCL stream to + */ + public PCLGenerator(OutputStream out) { + this.out = out; + } + + /** + * Main constructor. + * @param out the OutputStream to write the PCL stream to + * @param maxResolution the maximum resolution to encode bitmap images at + */ + public PCLGenerator(OutputStream out, int maxResolution) { + this(out); + boolean found = false; + for (int i = 0; i < PCL_RESOLUTIONS.length; i++) { + if (PCL_RESOLUTIONS[i] == maxResolution) { + found = true; + break; + } + } + if (!found) { + throw new IllegalArgumentException("Illegal value for maximum resolution!"); + } + this.maxBitmapResolution = maxResolution; + } + + /** @return the OutputStream that this generator writes to */ + public OutputStream getOutputStream() { + return this.out; + } + + /** @return the maximum resolution to encode bitmap images at */ + public int getMaximumBitmapResolution() { + return this.maxBitmapResolution; + } + + /** + * Writes a PCL escape command to the output stream. + * @param cmd the command (without the ESCAPE character) + * @throws IOException In case of an I/O error + */ + public void writeCommand(String cmd) throws IOException { + out.write(27); //ESC + out.write(cmd.getBytes("US-ASCII")); + } + + /** + * Writes raw text (in ISO-8859-1 encoding) to the output stream. + * @param s the text + * @throws IOException In case of an I/O error + */ + public void writeText(String s) throws IOException { + out.write(s.getBytes("ISO-8859-1")); + } + + /** + * Formats a double value with two decimal positions for PCL output. + * + * @param value value to format + * @return the formatted value + */ + public final String formatDouble2(double value) { + return df2.format(value); + } + + /** + * Formats a double value with four decimal positions for PCL output. + * + * @param value value to format + * @return the formatted value + */ + public final String formatDouble4(double value) { + return df4.format(value); + } + + /** + * Sends the universal end of language command (UEL). + * @throws IOException In case of an I/O error + */ + public void universalEndOfLanguage() throws IOException { + writeCommand("%-12345X"); + } + + /** + * Resets the printer and restores the user default environment. + * @throws IOException In case of an I/O error + */ + public void resetPrinter() throws IOException { + writeCommand("E"); + } + + /** + * Sends the job separation command. + * @throws IOException In case of an I/O error + */ + public void separateJobs() throws IOException { + writeCommand("&l1T"); + } + + /** + * Sends the form feed character. + * @throws IOException In case of an I/O error + */ + public void formFeed() throws IOException { + out.write(12); //=OC ("FF", Form feed) + } + + /** + * Sets the unit of measure. + * @param value the resolution value (units per inch) + * @throws IOException In case of an I/O error + */ + public void setUnitOfMeasure(int value) throws IOException { + writeCommand("&u" + value + "D"); + } + + /** + * Sets the raster graphics resolution + * @param value the resolution value (units per inch) + * @throws IOException In case of an I/O error + */ + public void setRasterGraphicsResolution(int value) throws IOException { + writeCommand("*t" + value + "R"); + } + + /** + * Selects the page size. + * @param selector the integer representing the page size + * @throws IOException In case of an I/O error + */ + public void selectPageSize(int selector) throws IOException { + writeCommand("&l" + selector + "A"); + } + + /** + * Selects the paper source. The parameter is usually printer-specific. Usually, "1" is the + * default tray, "2" is the manual paper feed, "3" is the manual envelope feed, "4" is the + * "lower" tray and "7" is "auto-select". Consult the technical reference for your printer + * for all available values. + * @param selector the integer representing the paper source/tray + * @throws IOException In case of an I/O error + */ + public void selectPaperSource(int selector) throws IOException { + writeCommand("&l" + selector + "H"); + } + + /** + * Clears the horizontal margins. + * @throws IOException In case of an I/O error + */ + public void clearHorizontalMargins() throws IOException { + writeCommand("9"); + } + + /** + * The Top Margin command designates the number of lines between + * the top of the logical page and the top of the text area. + * @param numberOfLines the number of lines (See PCL specification for details) + * @throws IOException In case of an I/O error + */ + public void setTopMargin(int numberOfLines) throws IOException { + writeCommand("&l" + numberOfLines + "E"); + } + + /** + * The Text Length command can be used to define the bottom border. See the PCL specification + * for details. + * @param numberOfLines the number of lines + * @throws IOException In case of an I/O error + */ + public void setTextLength(int numberOfLines) throws IOException { + writeCommand("&l" + numberOfLines + "F"); + } + + /** + * Sets the Vertical Motion Index (VMI). + * @param value the VMI value + * @throws IOException In case of an I/O error + */ + public void setVMI(double value) throws IOException { + writeCommand("&l" + formatDouble4(value) + "C"); + } + + /** + * Sets the cursor to a new absolute coordinate. + * @param x the X coordinate (in millipoints) + * @param y the Y coordinate (in millipoints) + * @throws IOException In case of an I/O error + */ + public void setCursorPos(double x, double y) throws IOException { + if (x < 0) { + //A negative x value will result in a relative movement so go to "0" first. + //But this will most probably have no effect anyway since you can't paint to the left + //of the logical page + writeCommand("&a0h" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V"); + } else { + writeCommand("&a" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V"); + } + } + + /** + * Pushes the current cursor position on a stack (stack size: max 20 entries) + * @throws IOException In case of an I/O error + */ + public void pushCursorPos() throws IOException { + writeCommand("&f0S"); + } + + /** + * Pops the current cursor position from the stack. + * @throws IOException In case of an I/O error + */ + public void popCursorPos() throws IOException { + writeCommand("&f1S"); + } + + /** + * Changes the current print direction while maintaining the current cursor position. + * @param rotate the rotation angle (counterclockwise), one of 0, 90, 180 and 270. + * @throws IOException In case of an I/O error + */ + public void changePrintDirection(int rotate) throws IOException { + writeCommand("&a" + rotate + "P"); + } + + /** + * Enters the HP GL/2 mode. + * @param restorePreviousHPGL2Cursor true if the previous HP GL/2 pen position should be + * restored, false if the current position is maintained + * @throws IOException In case of an I/O error + */ + public void enterHPGL2Mode(boolean restorePreviousHPGL2Cursor) throws IOException { + if (restorePreviousHPGL2Cursor) { + writeCommand("%0B"); + } else { + writeCommand("%1B"); + } + } + + /** + * Enters the PCL mode. + * @param restorePreviousPCLCursor true if the previous PCL cursor position should be restored, + * false if the current position is maintained + * @throws IOException In case of an I/O error + */ + public void enterPCLMode(boolean restorePreviousPCLCursor) throws IOException { + if (restorePreviousPCLCursor) { + writeCommand("%0A"); + } else { + writeCommand("%1A"); + } + } + + /** + * Generate a filled rectangle at the current cursor position. + * + * @param w the width in millipoints + * @param h the height in millipoints + * @param col the fill color + * @throws IOException In case of an I/O error + */ + protected void fillRect(int w, int h, Color col) throws IOException { + if ((w == 0) || (h == 0)) { + return; + } + if (h < 0) { + h *= -1; + } else { + //y += h; + } + + setPatternTransparencyMode(false); + writeCommand("*c" + formatDouble4(w / 100) + "h" + + formatDouble4(h / 100) + "V"); + int lineshade = convertToPCLShade(col); + writeCommand("*c" + lineshade + "G"); + writeCommand("*c2P"); + // Reset pattern transparency mode. + setPatternTransparencyMode(true); + } + + /** + * Sets the source transparency mode. + * @param transparent true if transparent, false for opaque + * @throws IOException In case of an I/O error + */ + public void setSourceTransparencyMode(boolean transparent) throws IOException { + setTransparencyMode(transparent, currentPatternTransparency); + } + + /** + * Sets the pattern transparency mode. + * @param transparent true if transparent, false for opaque + * @throws IOException In case of an I/O error + */ + public void setPatternTransparencyMode(boolean transparent) throws IOException { + setTransparencyMode(currentSourceTransparency, transparent); + } + + /** + * Sets the transparency modes. + * @param source source transparency: true if transparent, false for opaque + * @param pattern pattern transparency: true if transparent, false for opaque + * @throws IOException In case of an I/O error + */ + public void setTransparencyMode(boolean source, boolean pattern) throws IOException { + if (source != currentSourceTransparency && pattern != currentPatternTransparency) { + writeCommand("*v" + (source ? '0' : '1') + "n" + (pattern ? '0' : '1') + "O"); + } else if (source != currentSourceTransparency) { + writeCommand("*v" + (source ? '0' : '1') + "N"); + } else if (pattern != currentPatternTransparency) { + writeCommand("*v" + (pattern ? '0' : '1') + "O"); + } + this.currentSourceTransparency = source; + this.currentPatternTransparency = pattern; + } + + /** + * Convert an RGB color value to a grayscale from 0 to 100. + * @param r the red component + * @param g the green component + * @param b the blue component + * @return the gray value + */ + public final int convertToGray(int r, int g, int b) { + return (r * 30 + g * 59 + b * 11) / 100; + } + + /** + * Convert a Color value to a PCL shade value (0-100). + * @param col the color + * @return the PCL shade value (100=black) + */ + public final int convertToPCLShade(Color col) { + float gray = convertToGray(col.getRed(), col.getGreen(), col.getBlue()) / 255f; + return (int)(100 - (gray * 100f)); + } + + /** + * Select the current pattern + * @param patternID the pattern ID (<ESC>*c#G command) + * @param pattern the pattern type (<ESC>*v#T command) + * @throws IOException In case of an I/O error + */ + public void selectCurrentPattern(int patternID, int pattern) throws IOException { + if (pattern > 1) { + writeCommand("*c" + patternID + "G"); + } + writeCommand("*v" + pattern + "T"); + } + + /** + * Indicates whether an image is a monochrome (b/w) image. + * @param img the image + * @return true if it's a monochrome image + */ + public static boolean isMonochromeImage(RenderedImage img) { + ColorModel cm = img.getColorModel(); + if (cm instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)cm; + return icm.getMapSize() == 2; + } else { + return false; + } + } + + /** + * Indicates whether an image is a grayscale image. + * @param img the image + * @return true if it's a grayscale image + */ + public static boolean isGrayscaleImage(RenderedImage img) { + return (img.getColorModel().getColorSpace().getNumComponents() == 1); + } + + private MonochromeBitmapConverter createMonochromeBitmapConverter() { + MonochromeBitmapConverter converter = null; + try { + String clName = "org.apache.fop.render.pcl.JAIMonochromeBitmapConverter"; + Class clazz = Class.forName(clName); + converter = (MonochromeBitmapConverter)clazz.newInstance(); + } catch (ClassNotFoundException cnfe) { + // Class was not compiled so is not available. Simply ignore. + } catch (LinkageError le) { + // This can happen if fop was build with support for a + // particular provider (e.g. a binary fop distribution) + // but the required support files (i.e. JAI) are not + // available in the current runtime environment. + // Simply continue with the backup implementation. + } catch (InstantiationException e) { + // Problem instantiating the class, simply continue with the backup implementation + } catch (IllegalAccessException e) { + // Problem instantiating the class, simply continue with the backup implementation + } + if (converter == null) { + converter = new DefaultMonochromeBitmapConverter(); + } + return converter; + } + + private int calculatePCLResolution(int resolution) { + return calculatePCLResolution(resolution, false); + } + + /** + * Calculates the ideal PCL resolution for a given resolution. + * @param resolution the input resolution + * @param increased true if you want to go to a higher resolution, for example if you + * convert grayscale or color images to monochrome images so dithering has + * a chance to generate better quality. + * @return the resulting PCL resolution (one of 75, 100, 150, 200, 300, 600) + */ + private int calculatePCLResolution(int resolution, boolean increased) { + int choice = -1; + for (int i = PCL_RESOLUTIONS.length - 2; i >= 0; i--) { + if (resolution > PCL_RESOLUTIONS[i]) { + int idx = i + 1; + if (idx < PCL_RESOLUTIONS.length - 2) { + idx += increased ? 2 : 0; + } else if (idx < PCL_RESOLUTIONS.length - 1) { + idx += increased ? 1 : 0; + } + choice = idx; + break; + //return PCL_RESOLUTIONS[idx]; + } + } + if (choice < 0) { + choice = (increased ? 2 : 0); + } + while (choice > 0 && PCL_RESOLUTIONS[choice] > getMaximumBitmapResolution()) { + choice--; + } + return PCL_RESOLUTIONS[choice]; + } + + private boolean isValidPCLResolution(int resolution) { + return resolution == calculatePCLResolution(resolution); + } + + private Dimension getAdjustedDimension(Dimension orgDim, double orgResolution, + int pclResolution) { + if (orgResolution == pclResolution) { + return orgDim; + } else { + Dimension result = new Dimension(); + result.width = (int)Math.round((double)orgDim.width * pclResolution / orgResolution); + result.height = (int)Math.round((double)orgDim.height * pclResolution / orgResolution); + return result; + } + } + + //Threshold table to convert an alpha channel (8-bit) into a clip mask (1-bit) + private static final byte[] THRESHOLD_TABLE = new byte[256]; + static { // Initialize the arrays + for (int i = 0; i < 256; i++) { + THRESHOLD_TABLE[i] = (byte) ((i < 240) ? 255 : 0); + } + } + + private RenderedImage getMask(RenderedImage img, Dimension targetDim) { + ColorModel cm = img.getColorModel(); + if (cm.hasAlpha()) { + BufferedImage alpha = new BufferedImage(img.getWidth(), img.getHeight(), + BufferedImage.TYPE_BYTE_GRAY); + Raster raster = img.getData(); + GraphicsUtil.copyBand(raster, cm.getNumColorComponents(), alpha.getRaster(), 0); + + BufferedImageOp op1 = new LookupOp(new ByteLookupTable(0, THRESHOLD_TABLE), null); + BufferedImage alphat = op1.filter(alpha, null); + + BufferedImage mask; + if (true) { + mask = new BufferedImage(targetDim.width, targetDim.height, + BufferedImage.TYPE_BYTE_BINARY); + } else { + byte[] arr = {(byte)0, (byte)0xff}; + ColorModel colorModel = new IndexColorModel(1, 2, arr, arr, arr); + WritableRaster wraster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, + targetDim.width, targetDim.height, 1, 1, null); + mask = new BufferedImage(colorModel, wraster, false, null); + } + + Graphics2D g2d = mask.createGraphics(); + try { + AffineTransform at = new AffineTransform(); + double sx = targetDim.getWidth() / img.getWidth(); + double sy = targetDim.getHeight() / img.getHeight(); + at.scale(sx, sy); + g2d.drawRenderedImage(alphat, at); + } finally { + g2d.dispose(); + } + /* + try { + BatchDiffer.saveAsPNG(alpha, new java.io.File("D:/out-alpha.png")); + BatchDiffer.saveAsPNG(mask, new java.io.File("D:/out-mask.png")); + } catch (IOException e) { + e.printStackTrace(); + }*/ + return mask; + } else { + return null; + } + } + + /** + * Paint a bitmap at the current cursor position. The bitmap is converted to a monochrome + * (1-bit) bitmap image. + * @param img the bitmap image + * @param targetDim the target Dimention (in mpt) + * @param sourceTransparency true if the background should not be erased + * @throws IOException In case of an I/O error + */ + public void paintBitmap(RenderedImage img, Dimension targetDim, boolean sourceTransparency) + throws IOException { + double targetResolution = img.getWidth() / UnitConv.mpt2in(targetDim.width); + int resolution = (int)Math.round(targetResolution); + int effResolution = calculatePCLResolution(resolution, true); + Dimension orgDim = new Dimension(img.getWidth(), img.getHeight()); + Dimension effDim = getAdjustedDimension(orgDim, targetResolution, effResolution); + boolean scaled = !orgDim.equals(effDim); + + boolean monochrome = isMonochromeImage(img); + if (!monochrome) { + //Transparency mask disabled. Doesn't work reliably + final boolean transparencyDisabled = true; + RenderedImage mask = (transparencyDisabled ? null : getMask(img, effDim)); + if (mask != null) { + pushCursorPos(); + selectCurrentPattern(0, 1); //Solid white + setTransparencyMode(true, true); + paintMonochromeBitmap(mask, effResolution); + popCursorPos(); + } + + BufferedImage src = null; + if (img instanceof BufferedImage && !scaled) { + if (!isGrayscaleImage(img) || img.getColorModel().hasAlpha()) { + src = new BufferedImage(effDim.width, effDim.height, + BufferedImage.TYPE_BYTE_GRAY); + ColorConvertOp op = new ColorConvertOp( + ColorSpace.getInstance(ColorSpace.CS_GRAY), null); + op.filter((BufferedImage)img, src); + } else { + src = (BufferedImage)img; + } + } + if (src == null) { + src = new BufferedImage(effDim.width, effDim.height, + BufferedImage.TYPE_BYTE_GRAY); + Graphics2D g2d = src.createGraphics(); + try { + AffineTransform at = new AffineTransform(); + double sx = effDim.getWidth() / orgDim.getWidth(); + double sy = effDim.getHeight() / orgDim.getHeight(); + at.scale(sx, sy); + g2d.drawRenderedImage(img, at); + } finally { + g2d.dispose(); + } + } + MonochromeBitmapConverter converter = createMonochromeBitmapConverter(); + converter.setHint("quality", "false"); + + BufferedImage buf = (BufferedImage)converter.convertToMonochrome(src); + + RenderedImage red = buf; + selectCurrentPattern(0, 0); //Solid black + setTransparencyMode(sourceTransparency || mask != null, true); + paintMonochromeBitmap(red, effResolution); + } else { + //TODO untested! + RenderedImage effImg = img; + if (scaled) { + BufferedImage buf = new BufferedImage(effDim.width, effDim.height, + BufferedImage.TYPE_BYTE_BINARY); + Graphics2D g2d = buf.createGraphics(); + try { + AffineTransform at = new AffineTransform(); + double sx = effDim.getWidth() / orgDim.getWidth(); + double sy = effDim.getHeight() / orgDim.getHeight(); + at.scale(sx, sy); + g2d.drawRenderedImage(img, at); + } finally { + g2d.dispose(); + } + effImg = buf; + } + setSourceTransparencyMode(sourceTransparency); + selectCurrentPattern(0, 0); //Solid black + paintMonochromeBitmap(effImg, effResolution); + } + } + + /** + * Paint a bitmap at the current cursor position. The bitmap must be a monochrome + * (1-bit) bitmap image. + * @param img the bitmap image (must be 1-bit b/w) + * @param resolution the resolution of the image (must be a PCL resolution) + * @throws IOException In case of an I/O error + */ + public void paintMonochromeBitmap(RenderedImage img, int resolution) throws IOException { + if (!isValidPCLResolution(resolution)) { + throw new IllegalArgumentException("Invalid PCL resolution: " + resolution); + } + setRasterGraphicsResolution(resolution); + writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A"); + Raster raster = img.getData(); + boolean monochrome = isMonochromeImage(img); + if (!monochrome) { + throw new IllegalArgumentException("img must be a monochrome image"); + } + + int x = 0; + int y = 0; + int imgw = img.getWidth(); + int imgh = img.getHeight(); + int bytewidth = (imgw / 8); + if ((imgw % 8) != 0) { + bytewidth++; + } + byte ib; + byte[] rle = new byte[bytewidth * 2]; //compressed (RLE) + byte[] uncompressed = new byte[bytewidth]; //uncompressed + int lastcount = -1; + byte lastbyte = 0; + int rlewidth = 0; + + // Transfer graphics data + for (y = 0; y < imgh; y++) { + ib = 0; + for (x = 0; x < imgw; x++) { + int sample = raster.getSample(x, y, 0); + //Set image bit for black + if ((sample == 0)) { + ib |= (1 << (7 - (x % 8))); + } + + //RLE encoding + if ((x % 8) == 7 || ((x + 1) == imgw)) { + if (rlewidth < bytewidth) { + if (lastcount >= 0) { + if (ib == lastbyte) { + lastcount++; + } else { + rle[rlewidth++] = (byte)(lastcount & 0xFF); + rle[rlewidth++] = lastbyte; + lastbyte = ib; + lastcount = 0; + } + } else { + lastbyte = ib; + lastcount = 0; + } + if (lastcount == 255 || ((x + 1) == imgw)) { + rle[rlewidth++] = (byte)(lastcount & 0xFF); + rle[rlewidth++] = lastbyte; + lastbyte = 0; + lastcount = -1; + } + } + uncompressed[x / 8] = ib; + ib = 0; + } + } + if (rlewidth < bytewidth) { + writeCommand("*b1m" + rlewidth + "W"); + this.out.write(rle, 0, rlewidth); + } else { + writeCommand("*b0m" + bytewidth + "W"); + this.out.write(uncompressed); + } + lastcount = -1; + rlewidth = 0; + } + + // End raster graphics + writeCommand("*rB"); + } + +} |