| /* | |
| * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.print; | |
| import java.awt.Color; | |
| import java.awt.Component; | |
| import java.awt.Font; | |
| import java.awt.FontMetrics; | |
| import java.awt.GraphicsEnvironment; | |
| import java.awt.Graphics; | |
| import java.awt.Graphics2D; | |
| import java.awt.HeadlessException; | |
| import java.awt.Shape; | |
| import java.awt.font.FontRenderContext; | |
| import java.awt.geom.AffineTransform; | |
| import java.awt.geom.PathIterator; | |
| import java.awt.geom.Rectangle2D; | |
| import java.awt.image.BufferedImage; | |
| import java.awt.print.Pageable; | |
| import java.awt.print.PageFormat; | |
| import java.awt.print.Paper; | |
| import java.awt.print.Printable; | |
| import java.awt.print.PrinterException; | |
| import java.awt.print.PrinterIOException; | |
| import java.awt.print.PrinterJob; | |
| import javax.print.PrintService; | |
| import javax.print.StreamPrintService; | |
| import javax.print.attribute.HashPrintRequestAttributeSet; | |
| import javax.print.attribute.PrintRequestAttributeSet; | |
| import javax.print.attribute.PrintServiceAttributeSet; | |
| import javax.print.attribute.standard.PrinterName; | |
| import javax.print.attribute.standard.Copies; | |
| import javax.print.attribute.standard.Destination; | |
| import javax.print.attribute.standard.DialogTypeSelection; | |
| import javax.print.attribute.standard.JobName; | |
| import javax.print.attribute.standard.OutputBin; | |
| import javax.print.attribute.standard.Sides; | |
| import java.io.BufferedOutputStream; | |
| import java.io.BufferedReader; | |
| import java.io.File; | |
| import java.io.InputStream; | |
| import java.io.InputStreamReader; | |
| import java.io.IOException; | |
| import java.io.FileInputStream; | |
| import java.io.FileOutputStream; | |
| import java.io.OutputStream; | |
| import java.io.PrintStream; | |
| import java.io.PrintWriter; | |
| import java.io.StringWriter; | |
| import java.util.ArrayList; | |
| import java.util.Locale; | |
| import java.util.Properties; | |
| import sun.awt.CharsetString; | |
| import sun.awt.FontConfiguration; | |
| import sun.awt.OSInfo; | |
| import sun.awt.PlatformFont; | |
| import sun.awt.SunToolkit; | |
| import sun.font.FontAccess; | |
| import sun.font.FontUtilities; | |
| import java.nio.charset.*; | |
| import java.nio.CharBuffer; | |
| import java.nio.ByteBuffer; | |
| import java.nio.file.Files; | |
| //REMIND: Remove use of this class when IPPPrintService is moved to share directory. | |
| import java.lang.reflect.Method; | |
| import javax.print.attribute.Attribute; | |
| import javax.print.attribute.standard.JobSheets; | |
| import javax.print.attribute.standard.Media; | |
| /** | |
| * A class which initiates and executes a PostScript printer job. | |
| * | |
| * @author Richard Blanchard | |
| */ | |
| public class PSPrinterJob extends RasterPrinterJob { | |
| /* Class Constants */ | |
| /** | |
| * Passed to the {@code setFillMode} | |
| * method this value forces fills to be | |
| * done using the even-odd fill rule. | |
| */ | |
| protected static final int FILL_EVEN_ODD = 1; | |
| /** | |
| * Passed to the {@code setFillMode} | |
| * method this value forces fills to be | |
| * done using the non-zero winding rule. | |
| */ | |
| protected static final int FILL_WINDING = 2; | |
| /* PostScript has a 64K maximum on its strings. | |
| */ | |
| private static final int MAX_PSSTR = (1024 * 64 - 1); | |
| private static final int PS_XRES = 300; | |
| private static final int PS_YRES = 300; | |
| private static final String ADOBE_PS_STR = "%!PS-Adobe-3.0"; | |
| private static final String EOF_COMMENT = "%%EOF"; | |
| private static final String PAGE_COMMENT = "%%Page: "; | |
| private static final String READIMAGEPROC = "/imStr 0 def /imageSrc " + | |
| "{currentfile /ASCII85Decode filter /RunLengthDecode filter " + | |
| " imStr readstring pop } def"; | |
| private static final String COPIES = "/#copies exch def"; | |
| private static final String PAGE_SAVE = "/pgSave save def"; | |
| private static final String PAGE_RESTORE = "pgSave restore"; | |
| private static final String SHOWPAGE = "showpage"; | |
| private static final String IMAGE_SAVE = "/imSave save def"; | |
| private static final String IMAGE_STR = " string /imStr exch def"; | |
| private static final String IMAGE_RESTORE = "imSave restore"; | |
| private static final String SetFontName = "F"; | |
| private static final String DrawStringName = "S"; | |
| /** | |
| * The PostScript invocation to fill a path using the | |
| * even-odd rule. (eofill) | |
| */ | |
| private static final String EVEN_ODD_FILL_STR = "EF"; | |
| /** | |
| * The PostScript invocation to fill a path using the | |
| * non-zero winding rule. (fill) | |
| */ | |
| private static final String WINDING_FILL_STR = "WF"; | |
| /** | |
| * The PostScript to set the clip to be the current path | |
| * using the even odd rule. (eoclip) | |
| */ | |
| private static final String EVEN_ODD_CLIP_STR = "EC"; | |
| /** | |
| * The PostScript to set the clip to be the current path | |
| * using the non-zero winding rule. (clip) | |
| */ | |
| private static final String WINDING_CLIP_STR = "WC"; | |
| /** | |
| * Expecting two numbers on the PostScript stack, this | |
| * invocation moves the current pen position. (moveto) | |
| */ | |
| private static final String MOVETO_STR = " M"; | |
| /** | |
| * Expecting two numbers on the PostScript stack, this | |
| * invocation draws a PS line from the current pen | |
| * position to the point on the stack. (lineto) | |
| */ | |
| private static final String LINETO_STR = " L"; | |
| /** | |
| * This PostScript operator takes two control points | |
| * and an ending point and using the current pen | |
| * position as a starting point adds a bezier | |
| * curve to the current path. (curveto) | |
| */ | |
| private static final String CURVETO_STR = " C"; | |
| /** | |
| * The PostScript to pop a state off of the printer's | |
| * gstate stack. (grestore) | |
| */ | |
| private static final String GRESTORE_STR = "R"; | |
| /** | |
| * The PostScript to push a state on to the printer's | |
| * gstate stack. (gsave) | |
| */ | |
| private static final String GSAVE_STR = "G"; | |
| /** | |
| * Make the current PostScript path an empty path. (newpath) | |
| */ | |
| private static final String NEWPATH_STR = "N"; | |
| /** | |
| * Close the current subpath by generating a line segment | |
| * from the current position to the start of the subpath. (closepath) | |
| */ | |
| private static final String CLOSEPATH_STR = "P"; | |
| /** | |
| * Use the three numbers on top of the PS operator | |
| * stack to set the rgb color. (setrgbcolor) | |
| */ | |
| private static final String SETRGBCOLOR_STR = " SC"; | |
| /** | |
| * Use the top number on the stack to set the printer's | |
| * current gray value. (setgray) | |
| */ | |
| private static final String SETGRAY_STR = " SG"; | |
| /* Instance Variables */ | |
| private int mDestType; | |
| private String mDestination = "lp"; | |
| private boolean mNoJobSheet = false; | |
| private String mOptions; | |
| private Font mLastFont; | |
| private Color mLastColor; | |
| private Shape mLastClip; | |
| private AffineTransform mLastTransform; | |
| private double xres = PS_XRES; | |
| private double yres = PS_YRES; | |
| /** | |
| * The output stream to which the generated PostScript | |
| * is written. | |
| */ | |
| PrintStream mPSStream; | |
| /* The temporary file to which we spool before sending to the printer */ | |
| File spoolFile; | |
| /** | |
| * This string holds the PostScript operator to | |
| * be used to fill a path. It can be changed | |
| * by the {@code setFillMode} method. | |
| */ | |
| private String mFillOpStr = WINDING_FILL_STR; | |
| /** | |
| * This string holds the PostScript operator to | |
| * be used to clip to a path. It can be changed | |
| * by the {@code setFillMode} method. | |
| */ | |
| private String mClipOpStr = WINDING_CLIP_STR; | |
| /** | |
| * A stack that represents the PostScript gstate stack. | |
| */ | |
| ArrayList<GState> mGStateStack = new ArrayList<>(); | |
| /** | |
| * The x coordinate of the current pen position. | |
| */ | |
| private float mPenX; | |
| /** | |
| * The y coordinate of the current pen position. | |
| */ | |
| private float mPenY; | |
| /** | |
| * The x coordinate of the starting point of | |
| * the current subpath. | |
| */ | |
| private float mStartPathX; | |
| /** | |
| * The y coordinate of the starting point of | |
| * the current subpath. | |
| */ | |
| private float mStartPathY; | |
| /** | |
| * An optional mapping of fonts to PostScript names. | |
| */ | |
| private static Properties mFontProps = null; | |
| private static boolean isMac; | |
| /* Class static initialiser block */ | |
| static { | |
| initStatic(); | |
| } | |
| private static void initStatic() { | |
| mFontProps = initProps(); | |
| isMac = OSInfo.getOSType() == OSInfo.OSType.MACOSX; | |
| } | |
| /* | |
| * Initialize PostScript font properties. | |
| * Copied from PSPrintStream | |
| */ | |
| private static Properties initProps() { | |
| // search psfont.properties for fonts | |
| // and create and initialize fontProps if it exist. | |
| String jhome = System.getProperty("java.home"); | |
| if (jhome != null){ | |
| String ulocale = SunToolkit.getStartupLocale().getLanguage(); | |
| try { | |
| File f = new File(jhome + File.separator + | |
| "lib" + File.separator + | |
| "psfontj2d.properties." + ulocale); | |
| if (!f.canRead()){ | |
| f = new File(jhome + File.separator + | |
| "lib" + File.separator + | |
| "psfont.properties." + ulocale); | |
| if (!f.canRead()){ | |
| f = new File(jhome + File.separator + "lib" + | |
| File.separator + "psfontj2d.properties"); | |
| if (!f.canRead()){ | |
| f = new File(jhome + File.separator + "lib" + | |
| File.separator + "psfont.properties"); | |
| if (!f.canRead()){ | |
| return (Properties)null; | |
| } | |
| } | |
| } | |
| } | |
| // Load property file | |
| Properties props = new Properties(); | |
| try (FileInputStream in = new FileInputStream(f.getPath())) { | |
| props.load(in); | |
| } | |
| return props; | |
| } catch (Exception e){ | |
| return (Properties)null; | |
| } | |
| } | |
| return (Properties)null; | |
| } | |
| /* Constructors */ | |
| public PSPrinterJob() | |
| { | |
| } | |
| /* Instance Methods */ | |
| /** | |
| * Presents the user a dialog for changing properties of the | |
| * print job interactively. | |
| * @return false if the user cancels the dialog and | |
| * true otherwise. | |
| * @throws HeadlessException if GraphicsEnvironment.isHeadless() | |
| * returns true. | |
| * @see java.awt.GraphicsEnvironment#isHeadless | |
| */ | |
| public boolean printDialog() throws HeadlessException { | |
| if (GraphicsEnvironment.isHeadless()) { | |
| throw new HeadlessException(); | |
| } | |
| if (attributes == null) { | |
| attributes = new HashPrintRequestAttributeSet(); | |
| } | |
| attributes.add(new Copies(getCopies())); | |
| attributes.add(new JobName(getJobName(), null)); | |
| boolean doPrint = false; | |
| DialogTypeSelection dts = | |
| (DialogTypeSelection)attributes.get(DialogTypeSelection.class); | |
| if (dts == DialogTypeSelection.NATIVE) { | |
| // Remove DialogTypeSelection.NATIVE to prevent infinite loop in | |
| // RasterPrinterJob. | |
| attributes.remove(DialogTypeSelection.class); | |
| doPrint = printDialog(attributes); | |
| // restore attribute | |
| attributes.add(DialogTypeSelection.NATIVE); | |
| } else { | |
| doPrint = printDialog(attributes); | |
| } | |
| if (doPrint) { | |
| JobName jobName = (JobName)attributes.get(JobName.class); | |
| if (jobName != null) { | |
| setJobName(jobName.getValue()); | |
| } | |
| Copies copies = (Copies)attributes.get(Copies.class); | |
| if (copies != null) { | |
| setCopies(copies.getValue()); | |
| } | |
| Destination dest = (Destination)attributes.get(Destination.class); | |
| if (dest != null) { | |
| try { | |
| mDestType = RasterPrinterJob.FILE; | |
| mDestination = (new File(dest.getURI())).getPath(); | |
| } catch (Exception e) { | |
| mDestination = "out.ps"; | |
| } | |
| } else { | |
| mDestType = RasterPrinterJob.PRINTER; | |
| PrintService pServ = getPrintService(); | |
| if (pServ != null) { | |
| mDestination = pServ.getName(); | |
| if (isMac) { | |
| PrintServiceAttributeSet psaSet = pServ.getAttributes() ; | |
| if (psaSet != null) { | |
| mDestination = psaSet.get(PrinterName.class).toString(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return doPrint; | |
| } | |
| @Override | |
| protected void setAttributes(PrintRequestAttributeSet attributes) | |
| throws PrinterException { | |
| super.setAttributes(attributes); | |
| if (attributes == null) { | |
| return; // now always use attributes, so this shouldn't happen. | |
| } | |
| mOptions = ""; | |
| Attribute attr = attributes.get(Media.class); | |
| if (attr instanceof CustomMediaTray) { | |
| CustomMediaTray customTray = (CustomMediaTray)attr; | |
| String choice = customTray.getChoiceName(); | |
| if (choice != null) { | |
| mOptions += " InputSlot="+ choice; | |
| } | |
| } | |
| String outputBin = getOutputBinValue(outputBinAttr); | |
| if (outputBin != null) { | |
| mOptions += " output-bin=" + outputBin; | |
| } | |
| } | |
| /** | |
| * Invoked by the RasterPrinterJob super class | |
| * this method is called to mark the start of a | |
| * document. | |
| */ | |
| protected void startDoc() throws PrinterException { | |
| // A security check has been performed in the | |
| // java.awt.print.printerJob.getPrinterJob method. | |
| // We use an inner class to execute the privileged open operations. | |
| // Note that we only open a file if it has been nominated by | |
| // the end-user in a dialog that we ourselves put up. | |
| OutputStream output = null; | |
| if (getPrintService() instanceof PSStreamPrintService) { | |
| StreamPrintService sps = (StreamPrintService)getPrintService(); | |
| mDestType = RasterPrinterJob.STREAM; | |
| if (sps.isDisposed()) { | |
| throw new PrinterException("service is disposed"); | |
| } | |
| output = sps.getOutputStream(); | |
| if (output == null) { | |
| throw new PrinterException("Null output stream"); | |
| } | |
| } else { | |
| /* REMIND: This needs to be more maintainable */ | |
| mNoJobSheet = super.noJobSheet; | |
| if (super.destinationAttr != null) { | |
| mDestType = RasterPrinterJob.FILE; | |
| mDestination = super.destinationAttr; | |
| } | |
| if (mDestType == RasterPrinterJob.FILE) { | |
| try { | |
| spoolFile = new File(mDestination); | |
| output = new FileOutputStream(spoolFile); | |
| } catch (IOException ex) { | |
| abortDoc(); | |
| throw new PrinterIOException(ex); | |
| } | |
| } else { | |
| PrinterOpener po = new PrinterOpener(); | |
| po.run(); | |
| if (po.pex != null) { | |
| throw po.pex; | |
| } | |
| output = po.result; | |
| } | |
| } | |
| mPSStream = new PrintStream(new BufferedOutputStream(output)); | |
| mPSStream.println(ADOBE_PS_STR); | |
| mPSStream.println("%%BeginProlog"); | |
| mPSStream.println(READIMAGEPROC); | |
| mPSStream.println("/BD {bind def} bind def"); | |
| mPSStream.println("/D {def} BD"); | |
| mPSStream.println("/C {curveto} BD"); | |
| mPSStream.println("/L {lineto} BD"); | |
| mPSStream.println("/M {moveto} BD"); | |
| mPSStream.println("/R {grestore} BD"); | |
| mPSStream.println("/G {gsave} BD"); | |
| mPSStream.println("/N {newpath} BD"); | |
| mPSStream.println("/P {closepath} BD"); | |
| mPSStream.println("/EC {eoclip} BD"); | |
| mPSStream.println("/WC {clip} BD"); | |
| mPSStream.println("/EF {eofill} BD"); | |
| mPSStream.println("/WF {fill} BD"); | |
| mPSStream.println("/SG {setgray} BD"); | |
| mPSStream.println("/SC {setrgbcolor} BD"); | |
| mPSStream.println("/ISOF {"); | |
| mPSStream.println(" dup findfont dup length 1 add dict begin {"); | |
| mPSStream.println(" 1 index /FID eq {pop pop} {D} ifelse"); | |
| mPSStream.println(" } forall /Encoding ISOLatin1Encoding D"); | |
| mPSStream.println(" currentdict end definefont"); | |
| mPSStream.println("} BD"); | |
| mPSStream.println("/NZ {dup 1 lt {pop 1} if} BD"); | |
| /* The following procedure takes args: string, x, y, desiredWidth. | |
| * It calculates using stringwidth the width of the string in the | |
| * current font and subtracts it from the desiredWidth and divides | |
| * this by stringLen-1. This gives us a per-glyph adjustment in | |
| * the spacing needed (either +ve or -ve) to make the string | |
| * print at the desiredWidth. The ashow procedure call takes this | |
| * per-glyph adjustment as an argument. This is necessary for WYSIWYG | |
| */ | |
| mPSStream.println("/"+DrawStringName +" {"); | |
| mPSStream.println(" moveto 1 index stringwidth pop NZ sub"); | |
| mPSStream.println(" 1 index length 1 sub NZ div 0"); | |
| mPSStream.println(" 3 2 roll ashow newpath} BD"); | |
| mPSStream.println("/FL ["); | |
| if (mFontProps == null){ | |
| mPSStream.println(" /Helvetica ISOF"); | |
| mPSStream.println(" /Helvetica-Bold ISOF"); | |
| mPSStream.println(" /Helvetica-Oblique ISOF"); | |
| mPSStream.println(" /Helvetica-BoldOblique ISOF"); | |
| mPSStream.println(" /Times-Roman ISOF"); | |
| mPSStream.println(" /Times-Bold ISOF"); | |
| mPSStream.println(" /Times-Italic ISOF"); | |
| mPSStream.println(" /Times-BoldItalic ISOF"); | |
| mPSStream.println(" /Courier ISOF"); | |
| mPSStream.println(" /Courier-Bold ISOF"); | |
| mPSStream.println(" /Courier-Oblique ISOF"); | |
| mPSStream.println(" /Courier-BoldOblique ISOF"); | |
| } else { | |
| int cnt = Integer.parseInt(mFontProps.getProperty("font.num", "9")); | |
| for (int i = 0; i < cnt; i++){ | |
| mPSStream.println(" /" + mFontProps.getProperty | |
| ("font." + i, "Courier ISOF")); | |
| } | |
| } | |
| mPSStream.println("] D"); | |
| mPSStream.println("/"+SetFontName +" {"); | |
| mPSStream.println(" FL exch get exch scalefont"); | |
| mPSStream.println(" [1 0 0 -1 0 0] makefont setfont} BD"); | |
| mPSStream.println("%%EndProlog"); | |
| mPSStream.println("%%BeginSetup"); | |
| // Set Page Size using first page's format. | |
| PageFormat pageFormat = getPageable().getPageFormat(0); | |
| double paperHeight = pageFormat.getPaper().getHeight(); | |
| double paperWidth = pageFormat.getPaper().getWidth(); | |
| /* PostScript printers can always generate uncollated copies. | |
| */ | |
| mPSStream.print("<< /PageSize [" + | |
| paperWidth + " "+ paperHeight+"]"); | |
| final PrintService pservice = getPrintService(); | |
| Boolean isPS = Boolean.TRUE; | |
| try { | |
| Class<?> psClass = Class.forName("sun.print.IPPPrintService"); | |
| if (psClass.isInstance(pservice)) { | |
| Method isPSMethod = psClass.getMethod("isPostscript", | |
| (Class[])null); | |
| isPS = (Boolean)isPSMethod.invoke(pservice, (Object[])null); | |
| } | |
| } catch (Throwable t) { | |
| } | |
| if (isPS) { | |
| mPSStream.print(" /DeferredMediaSelection true"); | |
| } | |
| mPSStream.print(" /ImagingBBox null /ManualFeed false"); | |
| mPSStream.print(isCollated() ? " /Collate true":""); | |
| mPSStream.print(" /NumCopies " +getCopiesInt()); | |
| if (sidesAttr != Sides.ONE_SIDED) { | |
| if (sidesAttr == Sides.TWO_SIDED_LONG_EDGE) { | |
| mPSStream.print(" /Duplex true "); | |
| } else if (sidesAttr == Sides.TWO_SIDED_SHORT_EDGE) { | |
| mPSStream.print(" /Duplex true /Tumble true "); | |
| } | |
| } | |
| mPSStream.println(" >> setpagedevice "); | |
| mPSStream.println("%%EndSetup"); | |
| } | |
| // Inner class to open the printer output stream. | |
| private class PrinterOpener { | |
| PrinterException pex; | |
| OutputStream result; | |
| public OutputStream run() { | |
| try { | |
| /* Write to a temporary file which will be spooled to | |
| * the printer then deleted. In the case that the file | |
| * is not removed for some reason, request that it is | |
| * removed when the VM exits. | |
| */ | |
| spoolFile = Files.createTempFile("javaprint", ".ps").toFile(); | |
| spoolFile.deleteOnExit(); | |
| result = new FileOutputStream(spoolFile); | |
| return result; | |
| } catch (IOException ex) { | |
| // If there is an IOError we subvert it to a PrinterException. | |
| pex = new PrinterIOException(ex); | |
| } | |
| return null; | |
| } | |
| } | |
| // Inner class to invoke the system print command | |
| private class PrinterSpooler { | |
| PrinterException pex; | |
| private void handleProcessFailure(final Process failedProcess, | |
| final String[] execCmd, final int result) throws IOException { | |
| try (StringWriter sw = new StringWriter(); | |
| PrintWriter pw = new PrintWriter(sw)) { | |
| pw.append("error=").append(Integer.toString(result)); | |
| pw.append(" running:"); | |
| for (String arg: execCmd) { | |
| pw.append(" '").append(arg).append("'"); | |
| } | |
| try (InputStream is = failedProcess.getErrorStream(); | |
| InputStreamReader isr = new InputStreamReader(is); | |
| BufferedReader br = new BufferedReader(isr)) { | |
| while (br.ready()) { | |
| pw.println(); | |
| pw.append("\t\t").append(br.readLine()); | |
| } | |
| } finally { | |
| pw.flush(); | |
| } | |
| throw new IOException(sw.toString()); | |
| } | |
| } | |
| public Object run() { | |
| if (spoolFile == null || !spoolFile.exists()) { | |
| pex = new PrinterException("No spool file"); | |
| return null; | |
| } | |
| try { | |
| /** | |
| * Spool to the printer. | |
| */ | |
| String fileName = spoolFile.getAbsolutePath(); | |
| String[] execCmd = printExecCmd(mDestination, mOptions, | |
| mNoJobSheet, getJobNameInt(), | |
| 1, fileName); | |
| Process process = Runtime.getRuntime().exec(execCmd); | |
| process.waitFor(); | |
| final int result = process.exitValue(); | |
| if (0 != result) { | |
| handleProcessFailure(process, execCmd, result); | |
| } | |
| } catch (IOException ex) { | |
| pex = new PrinterIOException(ex); | |
| } catch (InterruptedException ie) { | |
| pex = new PrinterException(ie.toString()); | |
| } finally { | |
| spoolFile.delete(); | |
| } | |
| return null; | |
| } | |
| } | |
| /** | |
| * Invoked if the application cancelled the printjob. | |
| */ | |
| protected void abortDoc() { | |
| if (mPSStream != null && mDestType != RasterPrinterJob.STREAM) { | |
| mPSStream.close(); | |
| } | |
| if (spoolFile != null && spoolFile.exists()) { | |
| spoolFile.delete(); | |
| } | |
| } | |
| /** | |
| * Invoked by the RasterPrintJob super class | |
| * this method is called after that last page | |
| * has been imaged. | |
| */ | |
| protected void endDoc() throws PrinterException { | |
| if (mPSStream != null) { | |
| mPSStream.println(EOF_COMMENT); | |
| mPSStream.flush(); | |
| if (mPSStream.checkError()) { | |
| abortDoc(); | |
| throw new PrinterException("Error while writing to file"); | |
| } | |
| if (mDestType != RasterPrinterJob.STREAM) { | |
| mPSStream.close(); | |
| } | |
| } | |
| if (mDestType == RasterPrinterJob.PRINTER) { | |
| PrintService pServ = getPrintService(); | |
| if (pServ != null) { | |
| mDestination = pServ.getName(); | |
| if (isMac) { | |
| PrintServiceAttributeSet psaSet = pServ.getAttributes(); | |
| if (psaSet != null) { | |
| mDestination = psaSet.get(PrinterName.class).toString() ; | |
| } | |
| } | |
| } | |
| PrinterSpooler spooler = new PrinterSpooler(); | |
| spooler.run(); | |
| if (spooler.pex != null) { | |
| throw spooler.pex; | |
| } | |
| } | |
| } | |
| private String getCoordPrep() { | |
| return " 0 exch translate " | |
| + "1 -1 scale" | |
| + "[72 " + getXRes() + " div " | |
| + "0 0 " | |
| + "72 " + getYRes() + " div " | |
| + "0 0]concat"; | |
| } | |
| /** | |
| * The RasterPrintJob super class calls this method | |
| * at the start of each page. | |
| */ | |
| protected void startPage(PageFormat pageFormat, Printable painter, | |
| int index, boolean paperChanged) | |
| throws PrinterException | |
| { | |
| double paperHeight = pageFormat.getPaper().getHeight(); | |
| double paperWidth = pageFormat.getPaper().getWidth(); | |
| int pageNumber = index + 1; | |
| /* Place an initial gstate on to our gstate stack. | |
| * It will have the default PostScript gstate | |
| * attributes. | |
| */ | |
| mGStateStack = new ArrayList<>(); | |
| mGStateStack.add(new GState()); | |
| mPSStream.println(PAGE_COMMENT + pageNumber + " " + pageNumber); | |
| /* Check current page's pageFormat against the previous pageFormat, | |
| */ | |
| if (index > 0 && paperChanged) { | |
| mPSStream.print("<< /PageSize [" + | |
| paperWidth + " " + paperHeight + "]"); | |
| final PrintService pservice = getPrintService(); | |
| Boolean isPS = Boolean.TRUE; | |
| try { | |
| Class<?> psClass = Class.forName("sun.print.IPPPrintService"); | |
| if (psClass.isInstance(pservice)) { | |
| Method isPSMethod = | |
| psClass.getMethod("isPostscript", | |
| (Class[])null); | |
| isPS = (Boolean) isPSMethod.invoke(pservice, | |
| (Object[])null); | |
| } | |
| } catch (Throwable t) { | |
| } | |
| if (isPS) { | |
| mPSStream.print(" /DeferredMediaSelection true"); | |
| } | |
| mPSStream.println(" >> setpagedevice"); | |
| } | |
| mPSStream.println(PAGE_SAVE); | |
| mPSStream.println(paperHeight + getCoordPrep()); | |
| } | |
| /** | |
| * The RasterPrintJob super class calls this method | |
| * at the end of each page. | |
| */ | |
| protected void endPage(PageFormat format, Printable painter, | |
| int index) | |
| throws PrinterException | |
| { | |
| mPSStream.println(PAGE_RESTORE); | |
| mPSStream.println(SHOWPAGE); | |
| } | |
| /** | |
| * Convert the 24 bit BGR image buffer represented by | |
| * {@code image} to PostScript. The image is drawn at | |
| * {@code (destX, destY)} in device coordinates. | |
| * The image is scaled into a square of size | |
| * specified by {@code destWidth} and | |
| * {@code destHeight}. The portion of the | |
| * source image copied into that square is specified | |
| * by {@code srcX}, {@code srcY}, | |
| * {@code srcWidth}, and srcHeight. | |
| */ | |
| protected void drawImageBGR(byte[] bgrData, | |
| float destX, float destY, | |
| float destWidth, float destHeight, | |
| float srcX, float srcY, | |
| float srcWidth, float srcHeight, | |
| int srcBitMapWidth, int srcBitMapHeight) { | |
| /* We draw images at device resolution so we probably need | |
| * to change the current PostScript transform. | |
| */ | |
| setTransform(new AffineTransform()); | |
| prepDrawing(); | |
| int intSrcWidth = (int) srcWidth; | |
| int intSrcHeight = (int) srcHeight; | |
| mPSStream.println(IMAGE_SAVE); | |
| /* Create a PS string big enough to hold a row of pixels. | |
| */ | |
| int psBytesPerRow = 3 * intSrcWidth; | |
| while (psBytesPerRow > MAX_PSSTR) { | |
| psBytesPerRow /= 2; | |
| } | |
| mPSStream.println(psBytesPerRow + IMAGE_STR); | |
| /* Scale and translate the unit image. | |
| */ | |
| mPSStream.println("[" + destWidth + " 0 " | |
| + "0 " + destHeight | |
| + " " + destX + " " + destY | |
| +"]concat"); | |
| /* Color Image invocation. | |
| */ | |
| mPSStream.println(intSrcWidth + " " + intSrcHeight + " " + 8 + "[" | |
| + intSrcWidth + " 0 " | |
| + "0 " + intSrcHeight | |
| + " 0 " + 0 + "]" | |
| + "/imageSrc load false 3 colorimage"); | |
| /* Image data. | |
| */ | |
| int index = 0; | |
| byte[] rgbData = new byte[intSrcWidth * 3]; | |
| try { | |
| /* Skip the parts of the image that are not part | |
| * of the source rectangle. | |
| */ | |
| index = (int) srcY * srcBitMapWidth; | |
| for(int i = 0; i < intSrcHeight; i++) { | |
| /* Skip the left part of the image that is not | |
| * part of the source rectangle. | |
| */ | |
| index += (int) srcX; | |
| index = swapBGRtoRGB(bgrData, index, rgbData); | |
| byte[] encodedData = rlEncode(rgbData); | |
| byte[] asciiData = ascii85Encode(encodedData); | |
| mPSStream.write(asciiData); | |
| mPSStream.println(""); | |
| } | |
| /* | |
| * If there is an IOError we subvert it to a PrinterException. | |
| * Fix: There has got to be a better way, maybe define | |
| * a PrinterIOException and then throw that? | |
| */ | |
| } catch (IOException e) { | |
| //throw new PrinterException(e.toString()); | |
| } | |
| mPSStream.println(IMAGE_RESTORE); | |
| } | |
| /** | |
| * Prints the contents of the array of ints, 'data' | |
| * to the current page. The band is placed at the | |
| * location (x, y) in device coordinates on the | |
| * page. The width and height of the band is | |
| * specified by the caller. Currently the data | |
| * is 24 bits per pixel in BGR format. | |
| */ | |
| protected void printBand(byte[] bgrData, int x, int y, | |
| int width, int height) | |
| throws PrinterException | |
| { | |
| mPSStream.println(IMAGE_SAVE); | |
| /* Create a PS string big enough to hold a row of pixels. | |
| */ | |
| int psBytesPerRow = 3 * width; | |
| while (psBytesPerRow > MAX_PSSTR) { | |
| psBytesPerRow /= 2; | |
| } | |
| mPSStream.println(psBytesPerRow + IMAGE_STR); | |
| /* Scale and translate the unit image. | |
| */ | |
| mPSStream.println("[" + width + " 0 " | |
| + "0 " + height | |
| + " " + x + " " + y | |
| +"]concat"); | |
| /* Color Image invocation. | |
| */ | |
| mPSStream.println(width + " " + height + " " + 8 + "[" | |
| + width + " 0 " | |
| + "0 " + -height | |
| + " 0 " + height + "]" | |
| + "/imageSrc load false 3 colorimage"); | |
| /* Image data. | |
| */ | |
| int index = 0; | |
| byte[] rgbData = new byte[width*3]; | |
| try { | |
| for(int i = 0; i < height; i++) { | |
| index = swapBGRtoRGB(bgrData, index, rgbData); | |
| byte[] encodedData = rlEncode(rgbData); | |
| byte[] asciiData = ascii85Encode(encodedData); | |
| mPSStream.write(asciiData); | |
| mPSStream.println(""); | |
| } | |
| } catch (IOException e) { | |
| throw new PrinterIOException(e); | |
| } | |
| mPSStream.println(IMAGE_RESTORE); | |
| } | |
| /** | |
| * Examine the metrics captured by the | |
| * {@code PeekGraphics} instance and | |
| * if capable of directly converting this | |
| * print job to the printer's control language | |
| * or the native OS's graphics primitives, then | |
| * return a {@code PSPathGraphics} to perform | |
| * that conversion. If there is not an object | |
| * capable of the conversion then return | |
| * {@code null}. Returning {@code null} | |
| * causes the print job to be rasterized. | |
| */ | |
| protected Graphics2D createPathGraphics(PeekGraphics peekGraphics, | |
| PrinterJob printerJob, | |
| Printable painter, | |
| PageFormat pageFormat, | |
| int pageIndex) { | |
| PSPathGraphics pathGraphics; | |
| PeekMetrics metrics = peekGraphics.getMetrics(); | |
| /* If the application has drawn anything that | |
| * out PathGraphics class can not handle then | |
| * return a null PathGraphics. | |
| */ | |
| if (forcePDL == false && (forceRaster == true | |
| || metrics.hasNonSolidColors() | |
| || metrics.hasCompositing())) { | |
| pathGraphics = null; | |
| } else { | |
| BufferedImage bufferedImage = new BufferedImage(8, 8, | |
| BufferedImage.TYPE_INT_RGB); | |
| Graphics2D bufferedGraphics = bufferedImage.createGraphics(); | |
| boolean canRedraw = peekGraphics.getAWTDrawingOnly() == false; | |
| pathGraphics = new PSPathGraphics(bufferedGraphics, printerJob, | |
| painter, pageFormat, pageIndex, | |
| canRedraw); | |
| } | |
| return pathGraphics; | |
| } | |
| /** | |
| * Intersect the gstate's current path with the | |
| * current clip and make the result the new clip. | |
| */ | |
| protected void selectClipPath() { | |
| mPSStream.println(mClipOpStr); | |
| } | |
| protected void setClip(Shape clip) { | |
| mLastClip = clip; | |
| } | |
| protected void setTransform(AffineTransform transform) { | |
| mLastTransform = transform; | |
| } | |
| /** | |
| * Set the current PostScript font. | |
| * Taken from outFont in PSPrintStream. | |
| */ | |
| protected boolean setFont(Font font) { | |
| mLastFont = font; | |
| return true; | |
| } | |
| /** | |
| * Given an array of CharsetStrings that make up a run | |
| * of text, this routine converts each CharsetString to | |
| * an index into our PostScript font list. If one or more | |
| * CharsetStrings can not be represented by a PostScript | |
| * font, then this routine will return a null array. | |
| */ | |
| private int[] getPSFontIndexArray(Font font, CharsetString[] charSet) { | |
| int[] psFont = null; | |
| if (mFontProps != null) { | |
| psFont = new int[charSet.length]; | |
| } | |
| for (int i = 0; i < charSet.length && psFont != null; i++){ | |
| /* Get the encoding of the run of text. | |
| */ | |
| CharsetString cs = charSet[i]; | |
| CharsetEncoder fontCS = cs.fontDescriptor.encoder; | |
| String charsetName = cs.fontDescriptor.getFontCharsetName(); | |
| /* | |
| * sun.awt.Symbol perhaps should return "symbol" for encoding. | |
| * Similarly X11Dingbats should return "dingbats" | |
| * Forced to check for win32 & x/unix names for these converters. | |
| */ | |
| if ("Symbol".equals(charsetName)) { | |
| charsetName = "symbol"; | |
| } else if ("WingDings".equals(charsetName) || | |
| "X11Dingbats".equals(charsetName)) { | |
| charsetName = "dingbats"; | |
| } else { | |
| charsetName = makeCharsetName(charsetName, cs.charsetChars); | |
| } | |
| int styleMask = font.getStyle() | | |
| FontUtilities.getFont2D(font).getStyle(); | |
| String style = FontConfiguration.getStyleString(styleMask); | |
| /* First we map the font name through the properties file. | |
| * This mapping provides alias names for fonts, for example, | |
| * "timesroman" is mapped to "serif". | |
| */ | |
| String fontName = font.getFamily().toLowerCase(Locale.ENGLISH); | |
| fontName = fontName.replace(' ', '_'); | |
| String name = mFontProps.getProperty(fontName, ""); | |
| /* Now map the alias name, character set name, and style | |
| * to a PostScript name. | |
| */ | |
| String psName = | |
| mFontProps.getProperty(name + "." + charsetName + "." + style, | |
| null); | |
| if (psName != null) { | |
| /* Get the PostScript font index for the PostScript font. | |
| */ | |
| try { | |
| psFont[i] = | |
| Integer.parseInt(mFontProps.getProperty(psName)); | |
| /* If there is no PostScript font for this font name, | |
| * then we want to terminate the loop and the method | |
| * indicating our failure. Setting the array to null | |
| * is used to indicate these failures. | |
| */ | |
| } catch(NumberFormatException e){ | |
| psFont = null; | |
| } | |
| /* There was no PostScript name for the font, character set, | |
| * and style so give up. | |
| */ | |
| } else { | |
| psFont = null; | |
| } | |
| } | |
| return psFont; | |
| } | |
| /* return of 0 means unsupported. Other return indicates the number | |
| * of distinct PS fonts needed to draw this text. This saves us | |
| * doing this processing one extra time. | |
| */ | |
| protected int platformFontCount(Font font, String str) { | |
| if (mFontProps == null) { | |
| return 0; | |
| } | |
| PlatformFont peer = (PlatformFont) FontAccess.getFontAccess() | |
| .getFontPeer(font); | |
| CharsetString[] acs = peer.makeMultiCharsetString(str, false); | |
| if (acs == null) { | |
| /* AWT can't convert all chars so use 2D path */ | |
| return 0; | |
| } | |
| int[] psFonts = getPSFontIndexArray(font, acs); | |
| return (psFonts == null) ? 0 : psFonts.length; | |
| } | |
| protected boolean textOut(Graphics g, String str, float x, float y, | |
| Font mLastFont, FontRenderContext frc, | |
| float width) { | |
| /* If we don't have fonts, use 2D path instead. */ | |
| if (mFontProps == null) { | |
| return false; | |
| } | |
| /* On-screen drawString renders most control chars as the missing | |
| * glyph and have the non-zero advance of that glyph. | |
| * Exceptions are \t, \n and \r which are considered zero-width. | |
| * Postscript handles control chars mostly as a missing glyph. | |
| * But we use 'ashow' specifying a width for the string which | |
| * assumes zero-width for those three exceptions, and Postscript | |
| * tries to squeeze the extra char in, with the result that the | |
| * glyphs look compressed or even overlap. | |
| * So exclude those control chars from the string sent to PS. | |
| */ | |
| str = removeControlChars(str); | |
| if (str.isEmpty()) { | |
| return true; | |
| } | |
| /* If AWT can't convert all chars, use 2D path instead. */ | |
| FontAccess access = FontAccess.getFontAccess(); | |
| PlatformFont peer = (PlatformFont) access.getFontPeer(mLastFont); | |
| CharsetString[] acs = peer.makeMultiCharsetString(str, false); | |
| if (acs == null) { | |
| return false; | |
| } | |
| /* Get an array of indices into our PostScript name | |
| * table. If all of the runs can not be converted | |
| * to PostScript fonts then null is returned and | |
| * we'll want to fall back to printing the text | |
| * as shapes. | |
| */ | |
| int[] psFonts = getPSFontIndexArray(mLastFont, acs); | |
| if (psFonts == null) { | |
| return false; | |
| } | |
| /* Prepare graphics context, now that we know we can handle the text. */ | |
| prepDrawing(); | |
| /* Draw each string segment. */ | |
| for (int i = 0; i < acs.length; i++){ | |
| CharsetString cs = acs[i]; | |
| CharsetEncoder fontCS = cs.fontDescriptor.encoder; | |
| StringBuilder nativeStr = new StringBuilder(); | |
| byte[] strSeg = new byte[cs.length * 2]; | |
| int len = 0; | |
| try { | |
| ByteBuffer bb = ByteBuffer.wrap(strSeg); | |
| fontCS.encode(CharBuffer.wrap(cs.charsetChars, | |
| cs.offset, | |
| cs.length), | |
| bb, true); | |
| bb.flip(); | |
| len = bb.limit(); | |
| } catch (IllegalStateException | CoderMalfunctionError xx){ | |
| continue; | |
| } | |
| /* The width to fit to may either be specified, | |
| * or calculated. Specifying by the caller is only | |
| * valid if the text does not need to be decomposed | |
| * into multiple calls. | |
| */ | |
| float desiredWidth; | |
| if (acs.length == 1 && width != 0f) { | |
| desiredWidth = width; | |
| } else { | |
| Rectangle2D r2d = | |
| mLastFont.getStringBounds(cs.charsetChars, | |
| cs.offset, | |
| cs.offset+cs.length, | |
| frc); | |
| desiredWidth = (float)r2d.getWidth(); | |
| } | |
| /* unprintable chars had width of 0, causing a PS error | |
| */ | |
| if (desiredWidth == 0) { | |
| return true; | |
| } | |
| nativeStr.append('<'); | |
| for (int j = 0; j < len; j++){ | |
| byte b = strSeg[j]; | |
| // to avoid encoding conversion with println() | |
| String hexS = Integer.toHexString(b); | |
| int length = hexS.length(); | |
| if (length > 2) { | |
| hexS = hexS.substring(length - 2, length); | |
| } else if (length == 1) { | |
| hexS = "0" + hexS; | |
| } else if (length == 0) { | |
| hexS = "00"; | |
| } | |
| nativeStr.append(hexS); | |
| } | |
| nativeStr.append('>'); | |
| /* This comment costs too much in output file size */ | |
| // mPSStream.println("% Font[" + mLastFont.getName() + ", " + | |
| // FontConfiguration.getStyleString(mLastFont.getStyle()) + ", " | |
| // + mLastFont.getSize2D() + "]"); | |
| getGState().emitPSFont(psFonts[i], mLastFont.getSize2D()); | |
| // out String | |
| mPSStream.println(nativeStr.toString() + " " + | |
| desiredWidth + " " + x + " " + y + " " + | |
| DrawStringName); | |
| x += desiredWidth; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Set the current path rule to be either | |
| * {@code FILL_EVEN_ODD} (using the | |
| * even-odd file rule) or {@code FILL_WINDING} | |
| * (using the non-zero winding rule.) | |
| */ | |
| protected void setFillMode(int fillRule) { | |
| switch (fillRule) { | |
| case FILL_EVEN_ODD: | |
| mFillOpStr = EVEN_ODD_FILL_STR; | |
| mClipOpStr = EVEN_ODD_CLIP_STR; | |
| break; | |
| case FILL_WINDING: | |
| mFillOpStr = WINDING_FILL_STR; | |
| mClipOpStr = WINDING_CLIP_STR; | |
| break; | |
| default: | |
| throw new IllegalArgumentException(); | |
| } | |
| } | |
| /** | |
| * Set the printer's current color to be that | |
| * defined by {@code color} | |
| */ | |
| protected void setColor(Color color) { | |
| mLastColor = color; | |
| } | |
| /** | |
| * Fill the current path using the current fill mode | |
| * and color. | |
| */ | |
| protected void fillPath() { | |
| mPSStream.println(mFillOpStr); | |
| } | |
| /** | |
| * Called to mark the start of a new path. | |
| */ | |
| protected void beginPath() { | |
| prepDrawing(); | |
| mPSStream.println(NEWPATH_STR); | |
| mPenX = 0; | |
| mPenY = 0; | |
| } | |
| /** | |
| * Close the current subpath by appending a straight | |
| * line from the current point to the subpath's | |
| * starting point. | |
| */ | |
| protected void closeSubpath() { | |
| mPSStream.println(CLOSEPATH_STR); | |
| mPenX = mStartPathX; | |
| mPenY = mStartPathY; | |
| } | |
| /** | |
| * Generate PostScript to move the current pen | |
| * position to {@code (x, y)}. | |
| */ | |
| protected void moveTo(float x, float y) { | |
| mPSStream.println(trunc(x) + " " + trunc(y) + MOVETO_STR); | |
| /* moveto marks the start of a new subpath | |
| * and we need to remember that starting | |
| * position so that we know where the | |
| * pen returns to with a close path. | |
| */ | |
| mStartPathX = x; | |
| mStartPathY = y; | |
| mPenX = x; | |
| mPenY = y; | |
| } | |
| /** | |
| * Generate PostScript to draw a line from the | |
| * current pen position to {@code (x, y)}. | |
| */ | |
| protected void lineTo(float x, float y) { | |
| mPSStream.println(trunc(x) + " " + trunc(y) + LINETO_STR); | |
| mPenX = x; | |
| mPenY = y; | |
| } | |
| /** | |
| * Add to the current path a bezier curve formed | |
| * by the current pen position and the method parameters | |
| * which are two control points and an ending | |
| * point. | |
| */ | |
| protected void bezierTo(float control1x, float control1y, | |
| float control2x, float control2y, | |
| float endX, float endY) { | |
| // mPSStream.println(control1x + " " + control1y | |
| // + " " + control2x + " " + control2y | |
| // + " " + endX + " " + endY | |
| // + CURVETO_STR); | |
| mPSStream.println(trunc(control1x) + " " + trunc(control1y) | |
| + " " + trunc(control2x) + " " + trunc(control2y) | |
| + " " + trunc(endX) + " " + trunc(endY) | |
| + CURVETO_STR); | |
| mPenX = endX; | |
| mPenY = endY; | |
| } | |
| String trunc(float f) { | |
| float af = Math.abs(f); | |
| if (af >= 1f && af <=1000f) { | |
| f = Math.round(f*1000)/1000f; | |
| } | |
| return Float.toString(f); | |
| } | |
| /** | |
| * Return the x coordinate of the pen in the | |
| * current path. | |
| */ | |
| protected float getPenX() { | |
| return mPenX; | |
| } | |
| /** | |
| * Return the y coordinate of the pen in the | |
| * current path. | |
| */ | |
| protected float getPenY() { | |
| return mPenY; | |
| } | |
| /** | |
| * Return the x resolution of the coordinates | |
| * to be rendered. | |
| */ | |
| protected double getXRes() { | |
| return xres; | |
| } | |
| /** | |
| * Return the y resolution of the coordinates | |
| * to be rendered. | |
| */ | |
| protected double getYRes() { | |
| return yres; | |
| } | |
| /** | |
| * Set the resolution at which to print. | |
| */ | |
| protected void setXYRes(double x, double y) { | |
| xres = x; | |
| yres = y; | |
| } | |
| /** | |
| * For PostScript the origin is in the upper-left of the | |
| * paper not at the imageable area corner. | |
| */ | |
| protected double getPhysicalPrintableX(Paper p) { | |
| return 0; | |
| } | |
| /** | |
| * For PostScript the origin is in the upper-left of the | |
| * paper not at the imageable area corner. | |
| */ | |
| protected double getPhysicalPrintableY(Paper p) { | |
| return 0; | |
| } | |
| protected double getPhysicalPrintableWidth(Paper p) { | |
| return p.getImageableWidth(); | |
| } | |
| protected double getPhysicalPrintableHeight(Paper p) { | |
| return p.getImageableHeight(); | |
| } | |
| protected double getPhysicalPageWidth(Paper p) { | |
| return p.getWidth(); | |
| } | |
| protected double getPhysicalPageHeight(Paper p) { | |
| return p.getHeight(); | |
| } | |
| /** | |
| * Returns how many times each page in the book | |
| * should be consecutively printed by PrintJob. | |
| * If the printer makes copies itself then this | |
| * method should return 1. | |
| */ | |
| protected int getNoncollatedCopies() { | |
| return 1; | |
| } | |
| protected int getCollatedCopies() { | |
| return 1; | |
| } | |
| private String[] printExecCmd(String printer, String options, | |
| boolean noJobSheet, | |
| String jobTitle, int copies, String spoolFile) { | |
| int PRINTER = 0x1; | |
| int OPTIONS = 0x2; | |
| int JOBTITLE = 0x4; | |
| int COPIES = 0x8; | |
| int NOSHEET = 0x10; | |
| int pFlags = 0; | |
| String[] execCmd; | |
| int ncomps = 2; // minimum number of print args | |
| int n = 0; | |
| if (printer != null && !printer.isEmpty() && !printer.equals("lp")) { | |
| pFlags |= PRINTER; | |
| ncomps+=1; | |
| } | |
| if (options != null && !options.isEmpty()) { | |
| pFlags |= OPTIONS; | |
| ncomps+=options.trim().split(" ").length; | |
| } | |
| if (jobTitle != null && !jobTitle.isEmpty()) { | |
| pFlags |= JOBTITLE; | |
| ncomps+=1; | |
| } | |
| if (copies > 1) { | |
| pFlags |= COPIES; | |
| ncomps+=1; | |
| } | |
| if (noJobSheet) { | |
| pFlags |= NOSHEET; | |
| ncomps+=1; | |
| } else if (getPrintService(). | |
| isAttributeCategorySupported(JobSheets.class)) { | |
| ncomps+=1; // for jobsheet | |
| } | |
| if (OSInfo.getOSType() == OSInfo.OSType.LINUX || | |
| OSInfo.getOSType() == OSInfo.OSType.MACOSX) { | |
| execCmd = new String[ncomps]; | |
| execCmd[n++] = "/usr/bin/lpr"; | |
| if ((pFlags & PRINTER) != 0) { | |
| execCmd[n++] = "-P" + printer; | |
| } | |
| if ((pFlags & JOBTITLE) != 0) { | |
| execCmd[n++] = "-J" + jobTitle; | |
| } | |
| if ((pFlags & COPIES) != 0) { | |
| execCmd[n++] = "-#" + copies; | |
| } | |
| if ((pFlags & NOSHEET) != 0) { | |
| execCmd[n++] = "-h"; | |
| } else if (getPrintService(). | |
| isAttributeCategorySupported(JobSheets.class)) { | |
| execCmd[n++] = "-o job-sheets=standard"; | |
| } | |
| if ((pFlags & OPTIONS) != 0) { | |
| for (String option : options.trim().split(" ")) { | |
| execCmd[n++] = "-o " + option; | |
| } | |
| } | |
| } else { | |
| ncomps+=1; //add 1 arg for lp | |
| execCmd = new String[ncomps]; | |
| execCmd[n++] = "/usr/bin/lp"; | |
| execCmd[n++] = "-c"; // make a copy of the spool file | |
| if ((pFlags & PRINTER) != 0) { | |
| execCmd[n++] = "-d" + printer; | |
| } | |
| if ((pFlags & JOBTITLE) != 0) { | |
| execCmd[n++] = "-t" + jobTitle; | |
| } | |
| if ((pFlags & COPIES) != 0) { | |
| execCmd[n++] = "-n" + copies; | |
| } | |
| if ((pFlags & NOSHEET) != 0) { | |
| execCmd[n++] = "-o nobanner"; | |
| } else if (getPrintService(). | |
| isAttributeCategorySupported(JobSheets.class)) { | |
| execCmd[n++] = "-o job-sheets=standard"; | |
| } | |
| if ((pFlags & OPTIONS) != 0) { | |
| for (String option : options.trim().split(" ")) { | |
| execCmd[n++] = "-o " + option; | |
| } | |
| } | |
| } | |
| execCmd[n++] = spoolFile; | |
| return execCmd; | |
| } | |
| private static int swapBGRtoRGB(byte[] image, int index, byte[] dest) { | |
| int destIndex = 0; | |
| while(index < image.length-2 && destIndex < dest.length-2) { | |
| dest[destIndex++] = image[index+2]; | |
| dest[destIndex++] = image[index+1]; | |
| dest[destIndex++] = image[index+0]; | |
| index+=3; | |
| } | |
| return index; | |
| } | |
| /* | |
| * Currently CharToByteConverter.getCharacterEncoding() return values are | |
| * not fixed yet. These are used as the part of the key of | |
| * psfont.properties. When those name are fixed this routine can | |
| * be erased. | |
| */ | |
| private String makeCharsetName(String name, char[] chs) { | |
| if (name.equals("Cp1252") || name.equals("ISO8859_1")) { | |
| return "latin1"; | |
| } else if (name.equals("UTF8")) { | |
| // same as latin 1 if all chars < 256 | |
| for (int i=0; i < chs.length; i++) { | |
| if (chs[i] > 255) { | |
| return name.toLowerCase(); | |
| } | |
| } | |
| return "latin1"; | |
| } else if (name.startsWith("ISO8859")) { | |
| // same as latin 1 if all chars < 128 | |
| for (int i=0; i < chs.length; i++) { | |
| if (chs[i] > 127) { | |
| return name.toLowerCase(); | |
| } | |
| } | |
| return "latin1"; | |
| } else { | |
| return name.toLowerCase(); | |
| } | |
| } | |
| private void prepDrawing() { | |
| /* Pop gstates until we can set the needed clip | |
| * and transform or until we are at the outer most | |
| * gstate. | |
| */ | |
| while (isOuterGState() == false | |
| && (getGState().canSetClip(mLastClip) == false | |
| || getGState().mTransform.equals(mLastTransform) == false)) { | |
| grestore(); | |
| } | |
| /* Set the color. This can push the color to the | |
| * outer most gsave which is often a good thing. | |
| */ | |
| getGState().emitPSColor(mLastColor); | |
| /* We do not want to change the outermost | |
| * transform or clip so if we are at the | |
| * outer clip the generate a gsave. | |
| */ | |
| if (isOuterGState()) { | |
| gsave(); | |
| getGState().emitTransform(mLastTransform); | |
| getGState().emitPSClip(mLastClip); | |
| } | |
| /* Set the font if we have been asked to. It is | |
| * important that the font is set after the | |
| * transform in order to get the font size | |
| * correct. | |
| */ | |
| // if (g != null) { | |
| // getGState().emitPSFont(g, mLastFont); | |
| // } | |
| } | |
| /** | |
| * Return the GState that is currently on top | |
| * of the GState stack. There should always be | |
| * a GState on top of the stack. If there isn't | |
| * then this method will throw an IndexOutOfBounds | |
| * exception. | |
| */ | |
| private GState getGState() { | |
| int count = mGStateStack.size(); | |
| return mGStateStack.get(count - 1); | |
| } | |
| /** | |
| * Emit a PostScript gsave command and add a | |
| * new GState on to our stack which represents | |
| * the printer's gstate stack. | |
| */ | |
| private void gsave() { | |
| GState oldGState = getGState(); | |
| mGStateStack.add(new GState(oldGState)); | |
| mPSStream.println(GSAVE_STR); | |
| } | |
| /** | |
| * Emit a PostScript grestore command and remove | |
| * a GState from our stack which represents the | |
| * printer's gstate stack. | |
| */ | |
| private void grestore() { | |
| int count = mGStateStack.size(); | |
| mGStateStack.remove(count - 1); | |
| mPSStream.println(GRESTORE_STR); | |
| } | |
| /** | |
| * Return true if the current GState is the | |
| * outermost GState and therefore should not | |
| * be restored. | |
| */ | |
| private boolean isOuterGState() { | |
| return mGStateStack.size() == 1; | |
| } | |
| /** | |
| * A stack of GStates is maintained to model the printer's | |
| * gstate stack. Each GState holds information about | |
| * the current graphics attributes. | |
| */ | |
| private class GState{ | |
| Color mColor; | |
| Shape mClip; | |
| Font mFont; | |
| AffineTransform mTransform; | |
| GState() { | |
| mColor = Color.black; | |
| mClip = null; | |
| mFont = null; | |
| mTransform = new AffineTransform(); | |
| } | |
| GState(GState copyGState) { | |
| mColor = copyGState.mColor; | |
| mClip = copyGState.mClip; | |
| mFont = copyGState.mFont; | |
| mTransform = copyGState.mTransform; | |
| } | |
| boolean canSetClip(Shape clip) { | |
| return mClip == null || mClip.equals(clip); | |
| } | |
| void emitPSClip(Shape clip) { | |
| if (clip != null | |
| && (mClip == null || mClip.equals(clip) == false)) { | |
| String saveFillOp = mFillOpStr; | |
| String saveClipOp = mClipOpStr; | |
| convertToPSPath(clip.getPathIterator(new AffineTransform())); | |
| selectClipPath(); | |
| mClip = clip; | |
| /* The clip is a shape and has reset the winding rule state */ | |
| mClipOpStr = saveFillOp; | |
| mFillOpStr = saveFillOp; | |
| } | |
| } | |
| void emitTransform(AffineTransform transform) { | |
| if (transform != null && transform.equals(mTransform) == false) { | |
| double[] matrix = new double[6]; | |
| transform.getMatrix(matrix); | |
| mPSStream.println("[" + (float)matrix[0] | |
| + " " + (float)matrix[1] | |
| + " " + (float)matrix[2] | |
| + " " + (float)matrix[3] | |
| + " " + (float)matrix[4] | |
| + " " + (float)matrix[5] | |
| + "] concat"); | |
| mTransform = transform; | |
| } | |
| } | |
| void emitPSColor(Color color) { | |
| if (color != null && color.equals(mColor) == false) { | |
| float[] rgb = color.getRGBColorComponents(null); | |
| /* If the color is a gray value then use | |
| * setgray. | |
| */ | |
| if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) { | |
| mPSStream.println(rgb[0] + SETGRAY_STR); | |
| /* It's not gray so use setrgbcolor. | |
| */ | |
| } else { | |
| mPSStream.println(rgb[0] + " " | |
| + rgb[1] + " " | |
| + rgb[2] + " " | |
| + SETRGBCOLOR_STR); | |
| } | |
| mColor = color; | |
| } | |
| } | |
| void emitPSFont(int psFontIndex, float fontSize) { | |
| mPSStream.println(fontSize + " " + | |
| psFontIndex + " " + SetFontName); | |
| } | |
| } | |
| /** | |
| * Given a Java2D {@code PathIterator} instance, | |
| * this method translates that into a PostScript path.. | |
| */ | |
| void convertToPSPath(PathIterator pathIter) { | |
| float[] segment = new float[6]; | |
| int segmentType; | |
| /* Map the PathIterator's fill rule into the PostScript | |
| * fill rule. | |
| */ | |
| int fillRule; | |
| if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) { | |
| fillRule = FILL_EVEN_ODD; | |
| } else { | |
| fillRule = FILL_WINDING; | |
| } | |
| beginPath(); | |
| setFillMode(fillRule); | |
| while (pathIter.isDone() == false) { | |
| segmentType = pathIter.currentSegment(segment); | |
| switch (segmentType) { | |
| case PathIterator.SEG_MOVETO: | |
| moveTo(segment[0], segment[1]); | |
| break; | |
| case PathIterator.SEG_LINETO: | |
| lineTo(segment[0], segment[1]); | |
| break; | |
| /* Convert the quad path to a bezier. | |
| */ | |
| case PathIterator.SEG_QUADTO: | |
| float lastX = getPenX(); | |
| float lastY = getPenY(); | |
| float c1x = lastX + (segment[0] - lastX) * 2 / 3; | |
| float c1y = lastY + (segment[1] - lastY) * 2 / 3; | |
| float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3; | |
| float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3; | |
| bezierTo(c1x, c1y, | |
| c2x, c2y, | |
| segment[2], segment[3]); | |
| break; | |
| case PathIterator.SEG_CUBICTO: | |
| bezierTo(segment[0], segment[1], | |
| segment[2], segment[3], | |
| segment[4], segment[5]); | |
| break; | |
| case PathIterator.SEG_CLOSE: | |
| closeSubpath(); | |
| break; | |
| } | |
| pathIter.next(); | |
| } | |
| } | |
| /* | |
| * Fill the path defined by {@code pathIter} | |
| * with the specified color. | |
| * The path is provided in current user space. | |
| */ | |
| protected void deviceFill(PathIterator pathIter, Color color, | |
| AffineTransform tx, Shape clip) { | |
| if (pathIter.isDone() || | |
| Double.isNaN(tx.getScaleX()) || | |
| Double.isNaN(tx.getScaleY()) || | |
| Double.isNaN(tx.getShearX()) || | |
| Double.isNaN(tx.getShearY()) || | |
| Double.isNaN(tx.getTranslateX()) || | |
| Double.isNaN(tx.getTranslateY())) { | |
| return; | |
| } | |
| setTransform(tx); | |
| setClip(clip); | |
| setColor(color); | |
| convertToPSPath(pathIter); | |
| /* Specify the path to fill as the clip, this ensures that only | |
| * pixels which are inside the path will be filled, which is | |
| * what the Java 2D APIs specify | |
| */ | |
| mPSStream.println(GSAVE_STR); | |
| selectClipPath(); | |
| fillPath(); | |
| mPSStream.println(GRESTORE_STR + " " + NEWPATH_STR); | |
| } | |
| /* | |
| * Run length encode byte array in a form suitable for decoding | |
| * by the PS Level 2 filter RunLengthDecode. | |
| * Array data to encode is inArr. Encoded data is written to outArr | |
| * outArr must be long enough to hold the encoded data but this | |
| * can't be known ahead of time. | |
| * A safe assumption is to use double the length of the input array. | |
| * This is then copied into a new array of the correct length which | |
| * is returned. | |
| * Algorithm: | |
| * Encoding is a lead byte followed by data bytes. | |
| * Lead byte of 0->127 indicates leadByte + 1 distinct bytes follow | |
| * Lead byte of 129->255 indicates 257 - leadByte is the number of times | |
| * the following byte is repeated in the source. | |
| * 128 is a special lead byte indicating end of data (EOD) and is | |
| * written as the final byte of the returned encoded data. | |
| */ | |
| private byte[] rlEncode(byte[] inArr) { | |
| int inIndex = 0; | |
| int outIndex = 0; | |
| int startIndex = 0; | |
| int runLen = 0; | |
| byte[] outArr = new byte[(inArr.length * 2) +2]; | |
| while (inIndex < inArr.length) { | |
| if (runLen == 0) { | |
| startIndex = inIndex++; | |
| runLen=1; | |
| } | |
| while (runLen < 128 && inIndex < inArr.length && | |
| inArr[inIndex] == inArr[startIndex]) { | |
| runLen++; // count run of same value | |
| inIndex++; | |
| } | |
| if (runLen > 1) { | |
| outArr[outIndex++] = (byte)(257 - runLen); | |
| outArr[outIndex++] = inArr[startIndex]; | |
| runLen = 0; | |
| continue; // back to top of while loop. | |
| } | |
| // if reach here have a run of different values, or at the end. | |
| while (runLen < 128 && inIndex < inArr.length && | |
| inArr[inIndex] != inArr[inIndex-1]) { | |
| runLen++; // count run of different values | |
| inIndex++; | |
| } | |
| outArr[outIndex++] = (byte)(runLen - 1); | |
| for (int i = startIndex; i < startIndex+runLen; i++) { | |
| outArr[outIndex++] = inArr[i]; | |
| } | |
| runLen = 0; | |
| } | |
| outArr[outIndex++] = (byte)128; | |
| byte[] encodedData = new byte[outIndex]; | |
| System.arraycopy(outArr, 0, encodedData, 0, outIndex); | |
| return encodedData; | |
| } | |
| /* written acc. to Adobe Spec. "Filtered Files: ASCIIEncode Filter", | |
| * "PS Language Reference Manual, 2nd edition: Section 3.13" | |
| */ | |
| private byte[] ascii85Encode(byte[] inArr) { | |
| byte[] outArr = new byte[((inArr.length+4) * 5 / 4) + 2]; | |
| long p1 = 85; | |
| long p2 = p1*p1; | |
| long p3 = p1*p2; | |
| long p4 = p1*p3; | |
| byte pling = '!'; | |
| int i = 0; | |
| int olen = 0; | |
| long val, rem; | |
| while (i+3 < inArr.length) { | |
| val = ((long)((inArr[i++]&0xff))<<24) + | |
| ((long)((inArr[i++]&0xff))<<16) + | |
| ((long)((inArr[i++]&0xff))<< 8) + | |
| ((long)(inArr[i++]&0xff)); | |
| if (val == 0) { | |
| outArr[olen++] = 'z'; | |
| } else { | |
| rem = val; | |
| outArr[olen++] = (byte)(rem / p4 + pling); rem = rem % p4; | |
| outArr[olen++] = (byte)(rem / p3 + pling); rem = rem % p3; | |
| outArr[olen++] = (byte)(rem / p2 + pling); rem = rem % p2; | |
| outArr[olen++] = (byte)(rem / p1 + pling); rem = rem % p1; | |
| outArr[olen++] = (byte)(rem + pling); | |
| } | |
| } | |
| // input not a multiple of 4 bytes, write partial output. | |
| if (i < inArr.length) { | |
| int n = inArr.length - i; // n bytes remain to be written | |
| val = 0; | |
| while (i < inArr.length) { | |
| val = (val << 8) + (inArr[i++]&0xff); | |
| } | |
| int append = 4 - n; | |
| while (append-- > 0) { | |
| val = val << 8; | |
| } | |
| byte []c = new byte[5]; | |
| rem = val; | |
| c[0] = (byte)(rem / p4 + pling); rem = rem % p4; | |
| c[1] = (byte)(rem / p3 + pling); rem = rem % p3; | |
| c[2] = (byte)(rem / p2 + pling); rem = rem % p2; | |
| c[3] = (byte)(rem / p1 + pling); rem = rem % p1; | |
| c[4] = (byte)(rem + pling); | |
| for (int b = 0; b < n+1 ; b++) { | |
| outArr[olen++] = c[b]; | |
| } | |
| } | |
| // write EOD marker. | |
| outArr[olen++]='~'; outArr[olen++]='>'; | |
| /* The original intention was to insert a newline after every 78 bytes. | |
| * This was mainly intended for legibility but I decided against this | |
| * partially because of the (small) amount of extra space, and | |
| * partially because for line breaks either would have to hardwire | |
| * ascii 10 (newline) or calculate space in bytes to allocate for | |
| * the platform's newline byte sequence. Also need to be careful | |
| * about where its inserted: | |
| * Ascii 85 decoder ignores white space except for one special case: | |
| * you must ensure you do not split the EOD marker across lines. | |
| */ | |
| byte[] retArr = new byte[olen]; | |
| System.arraycopy(outArr, 0, retArr, 0, olen); | |
| return retArr; | |
| } | |
| } |