Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/print/PSPrinterJob.java
38829 views
/*1* Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.print;2627import java.awt.Color;28import java.awt.Component;29import java.awt.Font;30import java.awt.FontMetrics;31import java.awt.GraphicsEnvironment;32import java.awt.Graphics;33import java.awt.Graphics2D;34import java.awt.HeadlessException;35import java.awt.Rectangle;36import java.awt.Shape;3738import java.awt.image.BufferedImage;3940import java.awt.font.FontRenderContext;4142import java.awt.geom.AffineTransform;43import java.awt.geom.PathIterator;44import java.awt.geom.Rectangle2D;4546import java.awt.image.BufferedImage;4748import java.awt.print.Pageable;49import java.awt.print.PageFormat;50import java.awt.print.Paper;51import java.awt.print.Printable;52import java.awt.print.PrinterException;53import java.awt.print.PrinterIOException;54import java.awt.print.PrinterJob;5556import javax.print.DocFlavor;57import javax.print.PrintService;58import javax.print.StreamPrintService;59import javax.print.attribute.HashPrintRequestAttributeSet;60import javax.print.attribute.PrintRequestAttributeSet;61import javax.print.attribute.PrintServiceAttributeSet;62import javax.print.attribute.standard.PrinterName;63import javax.print.attribute.standard.Chromaticity;64import javax.print.attribute.standard.Copies;65import javax.print.attribute.standard.Destination;66import javax.print.attribute.standard.DialogTypeSelection;67import javax.print.attribute.standard.JobName;68import javax.print.attribute.standard.Sides;6970import java.io.BufferedInputStream;71import java.io.BufferedOutputStream;72import java.io.BufferedReader;73import java.io.CharConversionException;74import java.io.File;75import java.io.InputStream;76import java.io.InputStreamReader;77import java.io.IOException;78import java.io.FileInputStream;79import java.io.FileOutputStream;80import java.io.OutputStream;81import java.io.PrintStream;82import java.io.PrintWriter;83import java.io.StringWriter;8485import java.util.ArrayList;86import java.util.Enumeration;87import java.util.Locale;88import java.util.Properties;8990import sun.awt.CharsetString;91import sun.awt.FontConfiguration;92import sun.awt.FontDescriptor;93import sun.awt.PlatformFont;94import sun.awt.SunToolkit;95import sun.font.FontManagerFactory;96import sun.font.FontUtilities;9798import java.nio.charset.*;99import java.nio.CharBuffer;100import java.nio.ByteBuffer;101import java.nio.file.Files;102103//REMIND: Remove use of this class when IPPPrintService is moved to share directory.104import java.lang.reflect.Method;105106/**107* A class which initiates and executes a PostScript printer job.108*109* @author Richard Blanchard110*/111public class PSPrinterJob extends RasterPrinterJob {112113/* Class Constants */114115/**116* Passed to the <code>setFillMode</code>117* method this value forces fills to be118* done using the even-odd fill rule.119*/120protected static final int FILL_EVEN_ODD = 1;121122/**123* Passed to the <code>setFillMode</code>124* method this value forces fills to be125* done using the non-zero winding rule.126*/127protected static final int FILL_WINDING = 2;128129/* PostScript has a 64K maximum on its strings.130*/131private static final int MAX_PSSTR = (1024 * 64 - 1);132133private static final int RED_MASK = 0x00ff0000;134private static final int GREEN_MASK = 0x0000ff00;135private static final int BLUE_MASK = 0x000000ff;136137private static final int RED_SHIFT = 16;138private static final int GREEN_SHIFT = 8;139private static final int BLUE_SHIFT = 0;140141private static final int LOWNIBBLE_MASK = 0x0000000f;142private static final int HINIBBLE_MASK = 0x000000f0;143private static final int HINIBBLE_SHIFT = 4;144private static final byte hexDigits[] = {145(byte)'0', (byte)'1', (byte)'2', (byte)'3',146(byte)'4', (byte)'5', (byte)'6', (byte)'7',147(byte)'8', (byte)'9', (byte)'A', (byte)'B',148(byte)'C', (byte)'D', (byte)'E', (byte)'F'149};150151private static final int PS_XRES = 300;152private static final int PS_YRES = 300;153154private static final String ADOBE_PS_STR = "%!PS-Adobe-3.0";155private static final String EOF_COMMENT = "%%EOF";156private static final String PAGE_COMMENT = "%%Page: ";157158private static final String READIMAGEPROC = "/imStr 0 def /imageSrc " +159"{currentfile /ASCII85Decode filter /RunLengthDecode filter " +160" imStr readstring pop } def";161162private static final String COPIES = "/#copies exch def";163private static final String PAGE_SAVE = "/pgSave save def";164private static final String PAGE_RESTORE = "pgSave restore";165private static final String SHOWPAGE = "showpage";166private static final String IMAGE_SAVE = "/imSave save def";167private static final String IMAGE_STR = " string /imStr exch def";168private static final String IMAGE_RESTORE = "imSave restore";169170private static final String COORD_PREP = " 0 exch translate "171+ "1 -1 scale"172+ "[72 " + PS_XRES + " div "173+ "0 0 "174+ "72 " + PS_YRES + " div "175+ "0 0]concat";176177private static final String SetFontName = "F";178179private static final String DrawStringName = "S";180181/**182* The PostScript invocation to fill a path using the183* even-odd rule. (eofill)184*/185private static final String EVEN_ODD_FILL_STR = "EF";186187/**188* The PostScript invocation to fill a path using the189* non-zero winding rule. (fill)190*/191private static final String WINDING_FILL_STR = "WF";192193/**194* The PostScript to set the clip to be the current path195* using the even odd rule. (eoclip)196*/197private static final String EVEN_ODD_CLIP_STR = "EC";198199/**200* The PostScript to set the clip to be the current path201* using the non-zero winding rule. (clip)202*/203private static final String WINDING_CLIP_STR = "WC";204205/**206* Expecting two numbers on the PostScript stack, this207* invocation moves the current pen position. (moveto)208*/209private static final String MOVETO_STR = " M";210/**211* Expecting two numbers on the PostScript stack, this212* invocation draws a PS line from the current pen213* position to the point on the stack. (lineto)214*/215private static final String LINETO_STR = " L";216217/**218* This PostScript operator takes two control points219* and an ending point and using the current pen220* position as a starting point adds a bezier221* curve to the current path. (curveto)222*/223private static final String CURVETO_STR = " C";224225/**226* The PostScript to pop a state off of the printer's227* gstate stack. (grestore)228*/229private static final String GRESTORE_STR = "R";230/**231* The PostScript to push a state on to the printer's232* gstate stack. (gsave)233*/234private static final String GSAVE_STR = "G";235236/**237* Make the current PostScript path an empty path. (newpath)238*/239private static final String NEWPATH_STR = "N";240241/**242* Close the current subpath by generating a line segment243* from the current position to the start of the subpath. (closepath)244*/245private static final String CLOSEPATH_STR = "P";246247/**248* Use the three numbers on top of the PS operator249* stack to set the rgb color. (setrgbcolor)250*/251private static final String SETRGBCOLOR_STR = " SC";252253/**254* Use the top number on the stack to set the printer's255* current gray value. (setgray)256*/257private static final String SETGRAY_STR = " SG";258259/* Instance Variables */260261private int mDestType;262263private String mDestination = "lp";264265private boolean mNoJobSheet = false;266267private String mOptions;268269private Font mLastFont;270271private Color mLastColor;272273private Shape mLastClip;274275private AffineTransform mLastTransform;276277/* non-null if printing EPS for Java Plugin */278private EPSPrinter epsPrinter = null;279280/**281* The metrics for the font currently set.282*/283FontMetrics mCurMetrics;284285/**286* The output stream to which the generated PostScript287* is written.288*/289PrintStream mPSStream;290291/* The temporary file to which we spool before sending to the printer */292293File spoolFile;294295/**296* This string holds the PostScript operator to297* be used to fill a path. It can be changed298* by the <code>setFillMode</code> method.299*/300private String mFillOpStr = WINDING_FILL_STR;301302/**303* This string holds the PostScript operator to304* be used to clip to a path. It can be changed305* by the <code>setFillMode</code> method.306*/307private String mClipOpStr = WINDING_CLIP_STR;308309/**310* A stack that represents the PostScript gstate stack.311*/312ArrayList mGStateStack = new ArrayList();313314/**315* The x coordinate of the current pen position.316*/317private float mPenX;318319/**320* The y coordinate of the current pen position.321*/322private float mPenY;323324/**325* The x coordinate of the starting point of326* the current subpath.327*/328private float mStartPathX;329330/**331* The y coordinate of the starting point of332* the current subpath.333*/334private float mStartPathY;335336/**337* An optional mapping of fonts to PostScript names.338*/339private static Properties mFontProps = null;340341private static boolean isMac;342343/* Class static initialiser block */344static {345//enable priviledges so initProps can access system properties,346// open the property file, etc.347java.security.AccessController.doPrivileged(348new java.security.PrivilegedAction() {349public Object run() {350mFontProps = initProps();351String osName = System.getProperty("os.name");352isMac = osName.startsWith("Mac");353return null;354}355});356}357358/*359* Initialize PostScript font properties.360* Copied from PSPrintStream361*/362private static Properties initProps() {363// search psfont.properties for fonts364// and create and initialize fontProps if it exist.365366String jhome = System.getProperty("java.home");367368if (jhome != null){369String ulocale = SunToolkit.getStartupLocale().getLanguage();370try {371372File f = new File(jhome + File.separator +373"lib" + File.separator +374"psfontj2d.properties." + ulocale);375376if (!f.canRead()){377378f = new File(jhome + File.separator +379"lib" + File.separator +380"psfont.properties." + ulocale);381if (!f.canRead()){382383f = new File(jhome + File.separator + "lib" +384File.separator + "psfontj2d.properties");385386if (!f.canRead()){387388f = new File(jhome + File.separator + "lib" +389File.separator + "psfont.properties");390391if (!f.canRead()){392return (Properties)null;393}394}395}396}397398// Load property file399InputStream in =400new BufferedInputStream(new FileInputStream(f.getPath()));401Properties props = new Properties();402props.load(in);403in.close();404return props;405} catch (Exception e){406return (Properties)null;407}408}409return (Properties)null;410}411412/* Constructors */413414public PSPrinterJob()415{416}417418/* Instance Methods */419420/**421* Presents the user a dialog for changing properties of the422* print job interactively.423* @returns false if the user cancels the dialog and424* true otherwise.425* @exception HeadlessException if GraphicsEnvironment.isHeadless()426* returns true.427* @see java.awt.GraphicsEnvironment#isHeadless428*/429public boolean printDialog() throws HeadlessException {430431if (GraphicsEnvironment.isHeadless()) {432throw new HeadlessException();433}434435if (attributes == null) {436attributes = new HashPrintRequestAttributeSet();437}438attributes.add(new Copies(getCopies()));439attributes.add(new JobName(getJobName(), null));440441boolean doPrint = false;442DialogTypeSelection dts =443(DialogTypeSelection)attributes.get(DialogTypeSelection.class);444if (dts == DialogTypeSelection.NATIVE) {445// Remove DialogTypeSelection.NATIVE to prevent infinite loop in446// RasterPrinterJob.447attributes.remove(DialogTypeSelection.class);448doPrint = printDialog(attributes);449// restore attribute450attributes.add(DialogTypeSelection.NATIVE);451} else {452doPrint = printDialog(attributes);453}454455if (doPrint) {456JobName jobName = (JobName)attributes.get(JobName.class);457if (jobName != null) {458setJobName(jobName.getValue());459}460Copies copies = (Copies)attributes.get(Copies.class);461if (copies != null) {462setCopies(copies.getValue());463}464465Destination dest = (Destination)attributes.get(Destination.class);466467if (dest != null) {468try {469mDestType = RasterPrinterJob.FILE;470mDestination = (new File(dest.getURI())).getPath();471} catch (Exception e) {472mDestination = "out.ps";473}474} else {475mDestType = RasterPrinterJob.PRINTER;476PrintService pServ = getPrintService();477if (pServ != null) {478mDestination = pServ.getName();479if (isMac) {480PrintServiceAttributeSet psaSet = pServ.getAttributes() ;481if (psaSet != null) {482mDestination = psaSet.get(PrinterName.class).toString();483}484}485}486}487}488489return doPrint;490}491492/**493* Invoked by the RasterPrinterJob super class494* this method is called to mark the start of a495* document.496*/497protected void startDoc() throws PrinterException {498499// A security check has been performed in the500// java.awt.print.printerJob.getPrinterJob method.501// We use an inner class to execute the privilged open operations.502// Note that we only open a file if it has been nominated by503// the end-user in a dialog that we ouselves put up.504505OutputStream output;506507if (epsPrinter == null) {508if (getPrintService() instanceof PSStreamPrintService) {509StreamPrintService sps = (StreamPrintService)getPrintService();510mDestType = RasterPrinterJob.STREAM;511if (sps.isDisposed()) {512throw new PrinterException("service is disposed");513}514output = sps.getOutputStream();515if (output == null) {516throw new PrinterException("Null output stream");517}518} else {519/* REMIND: This needs to be more maintainable */520mNoJobSheet = super.noJobSheet;521if (super.destinationAttr != null) {522mDestType = RasterPrinterJob.FILE;523mDestination = super.destinationAttr;524}525if (mDestType == RasterPrinterJob.FILE) {526try {527spoolFile = new File(mDestination);528output = new FileOutputStream(spoolFile);529} catch (IOException ex) {530throw new PrinterIOException(ex);531}532} else {533PrinterOpener po = new PrinterOpener();534java.security.AccessController.doPrivileged(po);535if (po.pex != null) {536throw po.pex;537}538output = po.result;539}540}541542mPSStream = new PrintStream(new BufferedOutputStream(output));543mPSStream.println(ADOBE_PS_STR);544}545546mPSStream.println("%%BeginProlog");547mPSStream.println(READIMAGEPROC);548mPSStream.println("/BD {bind def} bind def");549mPSStream.println("/D {def} BD");550mPSStream.println("/C {curveto} BD");551mPSStream.println("/L {lineto} BD");552mPSStream.println("/M {moveto} BD");553mPSStream.println("/R {grestore} BD");554mPSStream.println("/G {gsave} BD");555mPSStream.println("/N {newpath} BD");556mPSStream.println("/P {closepath} BD");557mPSStream.println("/EC {eoclip} BD");558mPSStream.println("/WC {clip} BD");559mPSStream.println("/EF {eofill} BD");560mPSStream.println("/WF {fill} BD");561mPSStream.println("/SG {setgray} BD");562mPSStream.println("/SC {setrgbcolor} BD");563mPSStream.println("/ISOF {");564mPSStream.println(" dup findfont dup length 1 add dict begin {");565mPSStream.println(" 1 index /FID eq {pop pop} {D} ifelse");566mPSStream.println(" } forall /Encoding ISOLatin1Encoding D");567mPSStream.println(" currentdict end definefont");568mPSStream.println("} BD");569mPSStream.println("/NZ {dup 1 lt {pop 1} if} BD");570/* The following procedure takes args: string, x, y, desiredWidth.571* It calculates using stringwidth the width of the string in the572* current font and subtracts it from the desiredWidth and divides573* this by stringLen-1. This gives us a per-glyph adjustment in574* the spacing needed (either +ve or -ve) to make the string575* print at the desiredWidth. The ashow procedure call takes this576* per-glyph adjustment as an argument. This is necessary for WYSIWYG577*/578mPSStream.println("/"+DrawStringName +" {");579mPSStream.println(" moveto 1 index stringwidth pop NZ sub");580mPSStream.println(" 1 index length 1 sub NZ div 0");581mPSStream.println(" 3 2 roll ashow newpath} BD");582mPSStream.println("/FL [");583if (mFontProps == null){584mPSStream.println(" /Helvetica ISOF");585mPSStream.println(" /Helvetica-Bold ISOF");586mPSStream.println(" /Helvetica-Oblique ISOF");587mPSStream.println(" /Helvetica-BoldOblique ISOF");588mPSStream.println(" /Times-Roman ISOF");589mPSStream.println(" /Times-Bold ISOF");590mPSStream.println(" /Times-Italic ISOF");591mPSStream.println(" /Times-BoldItalic ISOF");592mPSStream.println(" /Courier ISOF");593mPSStream.println(" /Courier-Bold ISOF");594mPSStream.println(" /Courier-Oblique ISOF");595mPSStream.println(" /Courier-BoldOblique ISOF");596} else {597int cnt = Integer.parseInt(mFontProps.getProperty("font.num", "9"));598for (int i = 0; i < cnt; i++){599mPSStream.println(" /" + mFontProps.getProperty600("font." + String.valueOf(i), "Courier ISOF"));601}602}603mPSStream.println("] D");604605mPSStream.println("/"+SetFontName +" {");606mPSStream.println(" FL exch get exch scalefont");607mPSStream.println(" [1 0 0 -1 0 0] makefont setfont} BD");608609mPSStream.println("%%EndProlog");610611mPSStream.println("%%BeginSetup");612if (epsPrinter == null) {613// Set Page Size using first page's format.614PageFormat pageFormat = getPageable().getPageFormat(0);615double paperHeight = pageFormat.getPaper().getHeight();616double paperWidth = pageFormat.getPaper().getWidth();617618/* PostScript printers can always generate uncollated copies.619*/620mPSStream.print("<< /PageSize [" +621paperWidth + " "+ paperHeight+"]");622623final PrintService pservice = getPrintService();624Boolean isPS = (Boolean)java.security.AccessController.doPrivileged(625new java.security.PrivilegedAction() {626public Object run() {627try {628Class psClass = Class.forName("sun.print.IPPPrintService");629if (psClass.isInstance(pservice)) {630Method isPSMethod = psClass.getMethod("isPostscript",631(Class[])null);632return (Boolean)isPSMethod.invoke(pservice, (Object[])null);633}634} catch (Throwable t) {635}636return Boolean.TRUE;637}638}639);640if (isPS) {641mPSStream.print(" /DeferredMediaSelection true");642}643644mPSStream.print(" /ImagingBBox null /ManualFeed false");645mPSStream.print(isCollated() ? " /Collate true":"");646mPSStream.print(" /NumCopies " +getCopiesInt());647648if (sidesAttr != Sides.ONE_SIDED) {649if (sidesAttr == Sides.TWO_SIDED_LONG_EDGE) {650mPSStream.print(" /Duplex true ");651} else if (sidesAttr == Sides.TWO_SIDED_SHORT_EDGE) {652mPSStream.print(" /Duplex true /Tumble true ");653}654}655mPSStream.println(" >> setpagedevice ");656}657mPSStream.println("%%EndSetup");658}659660// Inner class to run "privileged" to open the printer output stream.661662private class PrinterOpener implements java.security.PrivilegedAction {663PrinterException pex;664OutputStream result;665666public Object run() {667try {668669/* Write to a temporary file which will be spooled to670* the printer then deleted. In the case that the file671* is not removed for some reason, request that it is672* removed when the VM exits.673*/674spoolFile = Files.createTempFile("javaprint", ".ps").toFile();675spoolFile.deleteOnExit();676677result = new FileOutputStream(spoolFile);678return result;679} catch (IOException ex) {680// If there is an IOError we subvert it to a PrinterException.681pex = new PrinterIOException(ex);682}683return null;684}685}686687// Inner class to run "privileged" to invoke the system print command688689private class PrinterSpooler implements java.security.PrivilegedAction {690PrinterException pex;691692private void handleProcessFailure(final Process failedProcess,693final String[] execCmd, final int result) throws IOException {694try (StringWriter sw = new StringWriter();695PrintWriter pw = new PrintWriter(sw)) {696pw.append("error=").append(Integer.toString(result));697pw.append(" running:");698for (String arg: execCmd) {699pw.append(" '").append(arg).append("'");700}701try (InputStream is = failedProcess.getErrorStream();702InputStreamReader isr = new InputStreamReader(is);703BufferedReader br = new BufferedReader(isr)) {704while (br.ready()) {705pw.println();706pw.append("\t\t").append(br.readLine());707}708} finally {709pw.flush();710throw new IOException(sw.toString());711}712}713}714715public Object run() {716if (spoolFile == null || !spoolFile.exists()) {717pex = new PrinterException("No spool file");718return null;719}720try {721/**722* Spool to the printer.723*/724String fileName = spoolFile.getAbsolutePath();725String execCmd[] = printExecCmd(mDestination, mOptions,726mNoJobSheet, getJobNameInt(),7271, fileName);728729Process process = Runtime.getRuntime().exec(execCmd);730process.waitFor();731final int result = process.exitValue();732if (0 != result) {733handleProcessFailure(process, execCmd, result);734}735} catch (IOException ex) {736pex = new PrinterIOException(ex);737} catch (InterruptedException ie) {738pex = new PrinterException(ie.toString());739} finally {740spoolFile.delete();741}742return null;743}744}745746747/**748* Invoked if the application cancelled the printjob.749*/750protected void abortDoc() {751if (mPSStream != null && mDestType != RasterPrinterJob.STREAM) {752mPSStream.close();753}754java.security.AccessController.doPrivileged(755new java.security.PrivilegedAction() {756757public Object run() {758if (spoolFile != null && spoolFile.exists()) {759spoolFile.delete();760}761return null;762}763});764}765766/**767* Invoked by the RasterPrintJob super class768* this method is called after that last page769* has been imaged.770*/771protected void endDoc() throws PrinterException {772if (mPSStream != null) {773mPSStream.println(EOF_COMMENT);774mPSStream.flush();775if (mDestType != RasterPrinterJob.STREAM) {776mPSStream.close();777}778}779if (mDestType == RasterPrinterJob.PRINTER) {780PrintService pServ = getPrintService();781if (pServ != null) {782mDestination = pServ.getName();783if (isMac) {784PrintServiceAttributeSet psaSet = pServ.getAttributes();785if (psaSet != null) {786mDestination = psaSet.get(PrinterName.class).toString() ;787}788}789}790PrinterSpooler spooler = new PrinterSpooler();791java.security.AccessController.doPrivileged(spooler);792if (spooler.pex != null) {793throw spooler.pex;794}795}796}797798/**799* The RasterPrintJob super class calls this method800* at the start of each page.801*/802protected void startPage(PageFormat pageFormat, Printable painter,803int index, boolean paperChanged)804throws PrinterException805{806double paperHeight = pageFormat.getPaper().getHeight();807double paperWidth = pageFormat.getPaper().getWidth();808int pageNumber = index + 1;809810/* Place an initial gstate on to our gstate stack.811* It will have the default PostScript gstate812* attributes.813*/814mGStateStack = new ArrayList();815mGStateStack.add(new GState());816817mPSStream.println(PAGE_COMMENT + pageNumber + " " + pageNumber);818819/* Check current page's pageFormat against the previous pageFormat,820*/821if (index > 0 && paperChanged) {822823mPSStream.print("<< /PageSize [" +824paperWidth + " " + paperHeight + "]");825826final PrintService pservice = getPrintService();827Boolean isPS =828(Boolean)java.security.AccessController.doPrivileged(829830new java.security.PrivilegedAction() {831public Object run() {832try {833Class psClass =834Class.forName("sun.print.IPPPrintService");835if (psClass.isInstance(pservice)) {836Method isPSMethod =837psClass.getMethod("isPostscript",838(Class[])null);839return (Boolean)840isPSMethod.invoke(pservice,841(Object[])null);842}843} catch (Throwable t) {844}845return Boolean.TRUE;846}847}848);849850if (isPS) {851mPSStream.print(" /DeferredMediaSelection true");852}853mPSStream.println(" >> setpagedevice");854}855mPSStream.println(PAGE_SAVE);856mPSStream.println(paperHeight + COORD_PREP);857}858859/**860* The RastePrintJob super class calls this method861* at the end of each page.862*/863protected void endPage(PageFormat format, Printable painter,864int index)865throws PrinterException866{867mPSStream.println(PAGE_RESTORE);868mPSStream.println(SHOWPAGE);869}870871/**872* Convert the 24 bit BGR image buffer represented by873* <code>image</code> to PostScript. The image is drawn at874* <code>(destX, destY)</code> in device coordinates.875* The image is scaled into a square of size876* specified by <code>destWidth</code> and877* <code>destHeight</code>. The portion of the878* source image copied into that square is specified879* by <code>srcX</code>, <code>srcY</code>,880* <code>srcWidth</code>, and srcHeight.881*/882protected void drawImageBGR(byte[] bgrData,883float destX, float destY,884float destWidth, float destHeight,885float srcX, float srcY,886float srcWidth, float srcHeight,887int srcBitMapWidth, int srcBitMapHeight) {888889/* We draw images at device resolution so we probably need890* to change the current PostScript transform.891*/892setTransform(new AffineTransform());893prepDrawing();894895int intSrcWidth = (int) srcWidth;896int intSrcHeight = (int) srcHeight;897898mPSStream.println(IMAGE_SAVE);899900/* Create a PS string big enough to hold a row of pixels.901*/902int psBytesPerRow = 3 * (int) intSrcWidth;903while (psBytesPerRow > MAX_PSSTR) {904psBytesPerRow /= 2;905}906907mPSStream.println(psBytesPerRow + IMAGE_STR);908909/* Scale and translate the unit image.910*/911mPSStream.println("[" + destWidth + " 0 "912+ "0 " + destHeight913+ " " + destX + " " + destY914+"]concat");915916/* Color Image invocation.917*/918mPSStream.println(intSrcWidth + " " + intSrcHeight + " " + 8 + "["919+ intSrcWidth + " 0 "920+ "0 " + intSrcHeight921+ " 0 " + 0 + "]"922+ "/imageSrc load false 3 colorimage");923924/* Image data.925*/926int index = 0;927byte[] rgbData = new byte[intSrcWidth * 3];928929try {930/* Skip the parts of the image that are not part931* of the source rectangle.932*/933index = (int) srcY * srcBitMapWidth;934935for(int i = 0; i < intSrcHeight; i++) {936937/* Skip the left part of the image that is not938* part of the source rectangle.939*/940index += (int) srcX;941942index = swapBGRtoRGB(bgrData, index, rgbData);943byte[] encodedData = rlEncode(rgbData);944byte[] asciiData = ascii85Encode(encodedData);945mPSStream.write(asciiData);946mPSStream.println("");947}948949/*950* If there is an IOError we subvert it to a PrinterException.951* Fix: There has got to be a better way, maybe define952* a PrinterIOException and then throw that?953*/954} catch (IOException e) {955//throw new PrinterException(e.toString());956}957958mPSStream.println(IMAGE_RESTORE);959}960961/**962* Prints the contents of the array of ints, 'data'963* to the current page. The band is placed at the964* location (x, y) in device coordinates on the965* page. The width and height of the band is966* specified by the caller. Currently the data967* is 24 bits per pixel in BGR format.968*/969protected void printBand(byte[] bgrData, int x, int y,970int width, int height)971throws PrinterException972{973974mPSStream.println(IMAGE_SAVE);975976/* Create a PS string big enough to hold a row of pixels.977*/978int psBytesPerRow = 3 * width;979while (psBytesPerRow > MAX_PSSTR) {980psBytesPerRow /= 2;981}982983mPSStream.println(psBytesPerRow + IMAGE_STR);984985/* Scale and translate the unit image.986*/987mPSStream.println("[" + width + " 0 "988+ "0 " + height989+ " " + x + " " + y990+"]concat");991992/* Color Image invocation.993*/994mPSStream.println(width + " " + height + " " + 8 + "["995+ width + " 0 "996+ "0 " + -height997+ " 0 " + height + "]"998+ "/imageSrc load false 3 colorimage");9991000/* Image data.1001*/1002int index = 0;1003byte[] rgbData = new byte[width*3];10041005try {1006for(int i = 0; i < height; i++) {1007index = swapBGRtoRGB(bgrData, index, rgbData);1008byte[] encodedData = rlEncode(rgbData);1009byte[] asciiData = ascii85Encode(encodedData);1010mPSStream.write(asciiData);1011mPSStream.println("");1012}10131014} catch (IOException e) {1015throw new PrinterIOException(e);1016}10171018mPSStream.println(IMAGE_RESTORE);1019}10201021/**1022* Examine the metrics captured by the1023* <code>PeekGraphics</code> instance and1024* if capable of directly converting this1025* print job to the printer's control language1026* or the native OS's graphics primitives, then1027* return a <code>PSPathGraphics</code> to perform1028* that conversion. If there is not an object1029* capable of the conversion then return1030* <code>null</code>. Returning <code>null</code>1031* causes the print job to be rasterized.1032*/10331034protected Graphics2D createPathGraphics(PeekGraphics peekGraphics,1035PrinterJob printerJob,1036Printable painter,1037PageFormat pageFormat,1038int pageIndex) {10391040PSPathGraphics pathGraphics;1041PeekMetrics metrics = peekGraphics.getMetrics();10421043/* If the application has drawn anything that1044* out PathGraphics class can not handle then1045* return a null PathGraphics.1046*/1047if (forcePDL == false && (forceRaster == true1048|| metrics.hasNonSolidColors()1049|| metrics.hasCompositing())) {10501051pathGraphics = null;1052} else {10531054BufferedImage bufferedImage = new BufferedImage(8, 8,1055BufferedImage.TYPE_INT_RGB);1056Graphics2D bufferedGraphics = bufferedImage.createGraphics();1057boolean canRedraw = peekGraphics.getAWTDrawingOnly() == false;10581059pathGraphics = new PSPathGraphics(bufferedGraphics, printerJob,1060painter, pageFormat, pageIndex,1061canRedraw);1062}10631064return pathGraphics;1065}10661067/**1068* Intersect the gstate's current path with the1069* current clip and make the result the new clip.1070*/1071protected void selectClipPath() {10721073mPSStream.println(mClipOpStr);1074}10751076protected void setClip(Shape clip) {10771078mLastClip = clip;1079}10801081protected void setTransform(AffineTransform transform) {1082mLastTransform = transform;1083}10841085/**1086* Set the current PostScript font.1087* Taken from outFont in PSPrintStream.1088*/1089protected boolean setFont(Font font) {1090mLastFont = font;1091return true;1092}10931094/**1095* Given an array of CharsetStrings that make up a run1096* of text, this routine converts each CharsetString to1097* an index into our PostScript font list. If one or more1098* CharsetStrings can not be represented by a PostScript1099* font, then this routine will return a null array.1100*/1101private int[] getPSFontIndexArray(Font font, CharsetString[] charSet) {1102int[] psFont = null;11031104if (mFontProps != null) {1105psFont = new int[charSet.length];1106}11071108for (int i = 0; i < charSet.length && psFont != null; i++){11091110/* Get the encoding of the run of text.1111*/1112CharsetString cs = charSet[i];11131114CharsetEncoder fontCS = cs.fontDescriptor.encoder;1115String charsetName = cs.fontDescriptor.getFontCharsetName();1116/*1117* sun.awt.Symbol perhaps should return "symbol" for encoding.1118* Similarly X11Dingbats should return "dingbats"1119* Forced to check for win32 & x/unix names for these converters.1120*/11211122if ("Symbol".equals(charsetName)) {1123charsetName = "symbol";1124} else if ("WingDings".equals(charsetName) ||1125"X11Dingbats".equals(charsetName)) {1126charsetName = "dingbats";1127} else {1128charsetName = makeCharsetName(charsetName, cs.charsetChars);1129}11301131int styleMask = font.getStyle() |1132FontUtilities.getFont2D(font).getStyle();11331134String style = FontConfiguration.getStyleString(styleMask);11351136/* First we map the font name through the properties file.1137* This mapping provides alias names for fonts, for example,1138* "timesroman" is mapped to "serif".1139*/1140String fontName = font.getFamily().toLowerCase(Locale.ENGLISH);1141fontName = fontName.replace(' ', '_');1142String name = mFontProps.getProperty(fontName, "");11431144/* Now map the alias name, character set name, and style1145* to a PostScript name.1146*/1147String psName =1148mFontProps.getProperty(name + "." + charsetName + "." + style,1149null);11501151if (psName != null) {11521153/* Get the PostScript font index for the PostScript font.1154*/1155try {1156psFont[i] =1157Integer.parseInt(mFontProps.getProperty(psName));11581159/* If there is no PostScript font for this font name,1160* then we want to termintate the loop and the method1161* indicating our failure. Setting the array to null1162* is used to indicate these failures.1163*/1164} catch(NumberFormatException e){1165psFont = null;1166}11671168/* There was no PostScript name for the font, character set,1169* and style so give up.1170*/1171} else {1172psFont = null;1173}1174}11751176return psFont;1177}117811791180private static String escapeParens(String str) {1181if (str.indexOf('(') == -1 && str.indexOf(')') == -1 ) {1182return str;1183} else {1184int count = 0;1185int pos = 0;1186while ((pos = str.indexOf('(', pos)) != -1) {1187count++;1188pos++;1189}1190pos = 0;1191while ((pos = str.indexOf(')', pos)) != -1) {1192count++;1193pos++;1194}1195char []inArr = str.toCharArray();1196char []outArr = new char[inArr.length+count];1197pos = 0;1198for (int i=0;i<inArr.length;i++) {1199if (inArr[i] == '(' || inArr[i] == ')') {1200outArr[pos++] = '\\';1201}1202outArr[pos++] = inArr[i];1203}1204return new String(outArr);12051206}1207}12081209/* return of 0 means unsupported. Other return indicates the number1210* of distinct PS fonts needed to draw this text. This saves us1211* doing this processing one extra time.1212*/1213protected int platformFontCount(Font font, String str) {1214if (mFontProps == null) {1215return 0;1216}1217CharsetString[] acs =1218((PlatformFont)(font.getPeer())).makeMultiCharsetString(str,false);1219if (acs == null) {1220/* AWT can't convert all chars so use 2D path */1221return 0;1222}1223int[] psFonts = getPSFontIndexArray(font, acs);1224return (psFonts == null) ? 0 : psFonts.length;1225}12261227protected boolean textOut(Graphics g, String str, float x, float y,1228Font mLastFont, FontRenderContext frc,1229float width) {1230boolean didText = true;12311232if (mFontProps == null) {1233return false;1234} else {1235prepDrawing();12361237/* On-screen drawString renders most control chars as the missing1238* glyph and have the non-zero advance of that glyph.1239* Exceptions are \t, \n and \r which are considered zero-width.1240* Postscript handles control chars mostly as a missing glyph.1241* But we use 'ashow' specifying a width for the string which1242* assumes zero-width for those three exceptions, and Postscript1243* tries to squeeze the extra char in, with the result that the1244* glyphs look compressed or even overlap.1245* So exclude those control chars from the string sent to PS.1246*/1247str = removeControlChars(str);1248if (str.length() == 0) {1249return true;1250}1251CharsetString[] acs =1252((PlatformFont)1253(mLastFont.getPeer())).makeMultiCharsetString(str, false);1254if (acs == null) {1255/* AWT can't convert all chars so use 2D path */1256return false;1257}1258/* Get an array of indices into our PostScript name1259* table. If all of the runs can not be converted1260* to PostScript fonts then null is returned and1261* we'll want to fall back to printing the text1262* as shapes.1263*/1264int[] psFonts = getPSFontIndexArray(mLastFont, acs);1265if (psFonts != null) {12661267for (int i = 0; i < acs.length; i++){1268CharsetString cs = acs[i];1269CharsetEncoder fontCS = cs.fontDescriptor.encoder;12701271StringBuffer nativeStr = new StringBuffer();1272byte[] strSeg = new byte[cs.length * 2];1273int len = 0;1274try {1275ByteBuffer bb = ByteBuffer.wrap(strSeg);1276fontCS.encode(CharBuffer.wrap(cs.charsetChars,1277cs.offset,1278cs.length),1279bb, true);1280bb.flip();1281len = bb.limit();1282} catch(IllegalStateException xx){1283continue;1284} catch(CoderMalfunctionError xx){1285continue;1286}1287/* The width to fit to may either be specified,1288* or calculated. Specifying by the caller is only1289* valid if the text does not need to be decomposed1290* into multiple calls.1291*/1292float desiredWidth;1293if (acs.length == 1 && width != 0f) {1294desiredWidth = width;1295} else {1296Rectangle2D r2d =1297mLastFont.getStringBounds(cs.charsetChars,1298cs.offset,1299cs.offset+cs.length,1300frc);1301desiredWidth = (float)r2d.getWidth();1302}1303/* unprintable chars had width of 0, causing a PS error1304*/1305if (desiredWidth == 0) {1306return didText;1307}1308nativeStr.append('<');1309for (int j = 0; j < len; j++){1310byte b = strSeg[j];1311// to avoid encoding conversion with println()1312String hexS = Integer.toHexString(b);1313int length = hexS.length();1314if (length > 2) {1315hexS = hexS.substring(length - 2, length);1316} else if (length == 1) {1317hexS = "0" + hexS;1318} else if (length == 0) {1319hexS = "00";1320}1321nativeStr.append(hexS);1322}1323nativeStr.append('>');1324/* This comment costs too much in output file size */1325// mPSStream.println("% Font[" + mLastFont.getName() + ", " +1326// FontConfiguration.getStyleString(mLastFont.getStyle()) + ", "1327// + mLastFont.getSize2D() + "]");1328getGState().emitPSFont(psFonts[i], mLastFont.getSize2D());13291330// out String1331mPSStream.println(nativeStr.toString() + " " +1332desiredWidth + " " + x + " " + y + " " +1333DrawStringName);1334x += desiredWidth;1335}1336} else {1337didText = false;1338}1339}13401341return didText;1342}1343/**1344* Set the current path rule to be either1345* <code>FILL_EVEN_ODD</code> (using the1346* even-odd file rule) or <code>FILL_WINDING</code>1347* (using the non-zero winding rule.)1348*/1349protected void setFillMode(int fillRule) {13501351switch (fillRule) {13521353case FILL_EVEN_ODD:1354mFillOpStr = EVEN_ODD_FILL_STR;1355mClipOpStr = EVEN_ODD_CLIP_STR;1356break;13571358case FILL_WINDING:1359mFillOpStr = WINDING_FILL_STR;1360mClipOpStr = WINDING_CLIP_STR;1361break;13621363default:1364throw new IllegalArgumentException();1365}13661367}13681369/**1370* Set the printer's current color to be that1371* defined by <code>color</code>1372*/1373protected void setColor(Color color) {1374mLastColor = color;1375}13761377/**1378* Fill the current path using the current fill mode1379* and color.1380*/1381protected void fillPath() {13821383mPSStream.println(mFillOpStr);1384}13851386/**1387* Called to mark the start of a new path.1388*/1389protected void beginPath() {13901391prepDrawing();1392mPSStream.println(NEWPATH_STR);13931394mPenX = 0;1395mPenY = 0;1396}13971398/**1399* Close the current subpath by appending a straight1400* line from the current point to the subpath's1401* starting point.1402*/1403protected void closeSubpath() {14041405mPSStream.println(CLOSEPATH_STR);14061407mPenX = mStartPathX;1408mPenY = mStartPathY;1409}141014111412/**1413* Generate PostScript to move the current pen1414* position to <code>(x, y)</code>.1415*/1416protected void moveTo(float x, float y) {14171418mPSStream.println(trunc(x) + " " + trunc(y) + MOVETO_STR);14191420/* moveto marks the start of a new subpath1421* and we need to remember that starting1422* position so that we know where the1423* pen returns to with a close path.1424*/1425mStartPathX = x;1426mStartPathY = y;14271428mPenX = x;1429mPenY = y;1430}1431/**1432* Generate PostScript to draw a line from the1433* current pen position to <code>(x, y)</code>.1434*/1435protected void lineTo(float x, float y) {14361437mPSStream.println(trunc(x) + " " + trunc(y) + LINETO_STR);14381439mPenX = x;1440mPenY = y;1441}14421443/**1444* Add to the current path a bezier curve formed1445* by the current pen position and the method parameters1446* which are two control points and an ending1447* point.1448*/1449protected void bezierTo(float control1x, float control1y,1450float control2x, float control2y,1451float endX, float endY) {14521453// mPSStream.println(control1x + " " + control1y1454// + " " + control2x + " " + control2y1455// + " " + endX + " " + endY1456// + CURVETO_STR);1457mPSStream.println(trunc(control1x) + " " + trunc(control1y)1458+ " " + trunc(control2x) + " " + trunc(control2y)1459+ " " + trunc(endX) + " " + trunc(endY)1460+ CURVETO_STR);146114621463mPenX = endX;1464mPenY = endY;1465}14661467String trunc(float f) {1468float af = Math.abs(f);1469if (af >= 1f && af <=1000f) {1470f = Math.round(f*1000)/1000f;1471}1472return Float.toString(f);1473}14741475/**1476* Return the x coordinate of the pen in the1477* current path.1478*/1479protected float getPenX() {14801481return mPenX;1482}1483/**1484* Return the y coordinate of the pen in the1485* current path.1486*/1487protected float getPenY() {14881489return mPenY;1490}14911492/**1493* Return the x resolution of the coordinates1494* to be rendered.1495*/1496protected double getXRes() {1497return PS_XRES;1498}1499/**1500* Return the y resolution of the coordinates1501* to be rendered.1502*/1503protected double getYRes() {1504return PS_YRES;1505}15061507/**1508* For PostScript the origin is in the upper-left of the1509* paper not at the imageable area corner.1510*/1511protected double getPhysicalPrintableX(Paper p) {1512return 0;15131514}15151516/**1517* For PostScript the origin is in the upper-left of the1518* paper not at the imageable area corner.1519*/1520protected double getPhysicalPrintableY(Paper p) {1521return 0;1522}15231524protected double getPhysicalPrintableWidth(Paper p) {1525return p.getImageableWidth();1526}15271528protected double getPhysicalPrintableHeight(Paper p) {1529return p.getImageableHeight();1530}15311532protected double getPhysicalPageWidth(Paper p) {1533return p.getWidth();1534}15351536protected double getPhysicalPageHeight(Paper p) {1537return p.getHeight();1538}15391540/**1541* Returns how many times each page in the book1542* should be consecutively printed by PrintJob.1543* If the printer makes copies itself then this1544* method should return 1.1545*/1546protected int getNoncollatedCopies() {1547return 1;1548}15491550protected int getCollatedCopies() {1551return 1;1552}15531554private String[] printExecCmd(String printer, String options,1555boolean noJobSheet,1556String banner, int copies, String spoolFile) {1557int PRINTER = 0x1;1558int OPTIONS = 0x2;1559int BANNER = 0x4;1560int COPIES = 0x8;1561int NOSHEET = 0x10;1562int pFlags = 0;1563String execCmd[];1564int ncomps = 2; // minimum number of print args1565int n = 0;15661567if (printer != null && !printer.equals("") && !printer.equals("lp")) {1568pFlags |= PRINTER;1569ncomps+=1;1570}1571if (options != null && !options.equals("")) {1572pFlags |= OPTIONS;1573ncomps+=1;1574}1575if (banner != null && !banner.equals("")) {1576pFlags |= BANNER;1577ncomps+=1;1578}1579if (copies > 1) {1580pFlags |= COPIES;1581ncomps+=1;1582}1583if (noJobSheet) {1584pFlags |= NOSHEET;1585ncomps+=1;1586}15871588String osname = System.getProperty("os.name");1589if (osname.equals("Linux") || osname.contains("OS X")) {1590execCmd = new String[ncomps];1591execCmd[n++] = "/usr/bin/lpr";1592if ((pFlags & PRINTER) != 0) {1593execCmd[n++] = "-P" + printer;1594}1595if ((pFlags & BANNER) != 0) {1596execCmd[n++] = "-J" + banner;1597}1598if ((pFlags & COPIES) != 0) {1599execCmd[n++] = "-#" + copies;1600}1601if ((pFlags & NOSHEET) != 0) {1602execCmd[n++] = "-h";1603}1604if ((pFlags & OPTIONS) != 0) {1605execCmd[n++] = new String(options);1606}1607} else {1608ncomps+=1; //add 1 arg for lp1609execCmd = new String[ncomps];1610execCmd[n++] = "/usr/bin/lp";1611execCmd[n++] = "-c"; // make a copy of the spool file1612if ((pFlags & PRINTER) != 0) {1613execCmd[n++] = "-d" + printer;1614}1615if ((pFlags & BANNER) != 0) {1616execCmd[n++] = "-t" + banner;1617}1618if ((pFlags & COPIES) != 0) {1619execCmd[n++] = "-n" + copies;1620}1621if ((pFlags & NOSHEET) != 0) {1622execCmd[n++] = "-o nobanner";1623}1624if ((pFlags & OPTIONS) != 0) {1625execCmd[n++] = "-o" + options;1626}1627}1628execCmd[n++] = spoolFile;1629return execCmd;1630}16311632private static int swapBGRtoRGB(byte[] image, int index, byte[] dest) {1633int destIndex = 0;1634while(index < image.length-2 && destIndex < dest.length-2) {1635dest[destIndex++] = image[index+2];1636dest[destIndex++] = image[index+1];1637dest[destIndex++] = image[index+0];1638index+=3;1639}1640return index;1641}16421643/*1644* Currently CharToByteConverter.getCharacterEncoding() return values are1645* not fixed yet. These are used as the part of the key of1646* psfont.properties. When those name are fixed this routine can1647* be erased.1648*/1649private String makeCharsetName(String name, char[] chs) {1650if (name.equals("Cp1252") || name.equals("ISO8859_1")) {1651return "latin1";1652} else if (name.equals("UTF8")) {1653// same as latin 1 if all chars < 2561654for (int i=0; i < chs.length; i++) {1655if (chs[i] > 255) {1656return name.toLowerCase();1657}1658}1659return "latin1";1660} else if (name.startsWith("ISO8859")) {1661// same as latin 1 if all chars < 1281662for (int i=0; i < chs.length; i++) {1663if (chs[i] > 127) {1664return name.toLowerCase();1665}1666}1667return "latin1";1668} else {1669return name.toLowerCase();1670}1671}16721673private void prepDrawing() {16741675/* Pop gstates until we can set the needed clip1676* and transform or until we are at the outer most1677* gstate.1678*/1679while (isOuterGState() == false1680&& (getGState().canSetClip(mLastClip) == false1681|| getGState().mTransform.equals(mLastTransform) == false)) {168216831684grestore();1685}16861687/* Set the color. This can push the color to the1688* outer most gsave which is often a good thing.1689*/1690getGState().emitPSColor(mLastColor);16911692/* We do not want to change the outermost1693* transform or clip so if we are at the1694* outer clip the generate a gsave.1695*/1696if (isOuterGState()) {1697gsave();1698getGState().emitTransform(mLastTransform);1699getGState().emitPSClip(mLastClip);1700}17011702/* Set the font if we have been asked to. It is1703* important that the font is set after the1704* transform in order to get the font size1705* correct.1706*/1707// if (g != null) {1708// getGState().emitPSFont(g, mLastFont);1709// }17101711}17121713/**1714* Return the GState that is currently on top1715* of the GState stack. There should always be1716* a GState on top of the stack. If there isn't1717* then this method will throw an IndexOutOfBounds1718* exception.1719*/1720private GState getGState() {1721int count = mGStateStack.size();1722return (GState) mGStateStack.get(count - 1);1723}17241725/**1726* Emit a PostScript gsave command and add a1727* new GState on to our stack which represents1728* the printer's gstate stack.1729*/1730private void gsave() {1731GState oldGState = getGState();1732mGStateStack.add(new GState(oldGState));1733mPSStream.println(GSAVE_STR);1734}17351736/**1737* Emit a PostScript grestore command and remove1738* a GState from our stack which represents the1739* printer's gstate stack.1740*/1741private void grestore() {1742int count = mGStateStack.size();1743mGStateStack.remove(count - 1);1744mPSStream.println(GRESTORE_STR);1745}17461747/**1748* Return true if the current GState is the1749* outermost GState and therefore should not1750* be restored.1751*/1752private boolean isOuterGState() {1753return mGStateStack.size() == 1;1754}17551756/**1757* A stack of GStates is maintained to model the printer's1758* gstate stack. Each GState holds information about1759* the current graphics attributes.1760*/1761private class GState{1762Color mColor;1763Shape mClip;1764Font mFont;1765AffineTransform mTransform;17661767GState() {1768mColor = Color.black;1769mClip = null;1770mFont = null;1771mTransform = new AffineTransform();1772}17731774GState(GState copyGState) {1775mColor = copyGState.mColor;1776mClip = copyGState.mClip;1777mFont = copyGState.mFont;1778mTransform = copyGState.mTransform;1779}17801781boolean canSetClip(Shape clip) {17821783return mClip == null || mClip.equals(clip);1784}178517861787void emitPSClip(Shape clip) {1788if (clip != null1789&& (mClip == null || mClip.equals(clip) == false)) {1790String saveFillOp = mFillOpStr;1791String saveClipOp = mClipOpStr;1792convertToPSPath(clip.getPathIterator(new AffineTransform()));1793selectClipPath();1794mClip = clip;1795/* The clip is a shape and has reset the winding rule state */1796mClipOpStr = saveFillOp;1797mFillOpStr = saveFillOp;1798}1799}18001801void emitTransform(AffineTransform transform) {18021803if (transform != null && transform.equals(mTransform) == false) {1804double[] matrix = new double[6];1805transform.getMatrix(matrix);1806mPSStream.println("[" + (float)matrix[0]1807+ " " + (float)matrix[1]1808+ " " + (float)matrix[2]1809+ " " + (float)matrix[3]1810+ " " + (float)matrix[4]1811+ " " + (float)matrix[5]1812+ "] concat");18131814mTransform = transform;1815}1816}18171818void emitPSColor(Color color) {1819if (color != null && color.equals(mColor) == false) {1820float[] rgb = color.getRGBColorComponents(null);18211822/* If the color is a gray value then use1823* setgray.1824*/1825if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) {1826mPSStream.println(rgb[0] + SETGRAY_STR);18271828/* It's not gray so use setrgbcolor.1829*/1830} else {1831mPSStream.println(rgb[0] + " "1832+ rgb[1] + " "1833+ rgb[2] + " "1834+ SETRGBCOLOR_STR);1835}18361837mColor = color;18381839}1840}18411842void emitPSFont(int psFontIndex, float fontSize) {1843mPSStream.println(fontSize + " " +1844psFontIndex + " " + SetFontName);1845}1846}18471848/**1849* Given a Java2D <code>PathIterator</code> instance,1850* this method translates that into a PostScript path..1851*/1852void convertToPSPath(PathIterator pathIter) {18531854float[] segment = new float[6];1855int segmentType;18561857/* Map the PathIterator's fill rule into the PostScript1858* fill rule.1859*/1860int fillRule;1861if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) {1862fillRule = FILL_EVEN_ODD;1863} else {1864fillRule = FILL_WINDING;1865}18661867beginPath();18681869setFillMode(fillRule);18701871while (pathIter.isDone() == false) {1872segmentType = pathIter.currentSegment(segment);18731874switch (segmentType) {1875case PathIterator.SEG_MOVETO:1876moveTo(segment[0], segment[1]);1877break;18781879case PathIterator.SEG_LINETO:1880lineTo(segment[0], segment[1]);1881break;18821883/* Convert the quad path to a bezier.1884*/1885case PathIterator.SEG_QUADTO:1886float lastX = getPenX();1887float lastY = getPenY();1888float c1x = lastX + (segment[0] - lastX) * 2 / 3;1889float c1y = lastY + (segment[1] - lastY) * 2 / 3;1890float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3;1891float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3;1892bezierTo(c1x, c1y,1893c2x, c2y,1894segment[2], segment[3]);1895break;18961897case PathIterator.SEG_CUBICTO:1898bezierTo(segment[0], segment[1],1899segment[2], segment[3],1900segment[4], segment[5]);1901break;19021903case PathIterator.SEG_CLOSE:1904closeSubpath();1905break;1906}190719081909pathIter.next();1910}1911}19121913/*1914* Fill the path defined by <code>pathIter</code>1915* with the specified color.1916* The path is provided in current user space.1917*/1918protected void deviceFill(PathIterator pathIter, Color color,1919AffineTransform tx, Shape clip) {19201921setTransform(tx);1922setClip(clip);1923setColor(color);1924convertToPSPath(pathIter);1925/* Specify the path to fill as the clip, this ensures that only1926* pixels which are inside the path will be filled, which is1927* what the Java 2D APIs specify1928*/1929mPSStream.println(GSAVE_STR);1930selectClipPath();1931fillPath();1932mPSStream.println(GRESTORE_STR + " " + NEWPATH_STR);1933}19341935/*1936* Run length encode byte array in a form suitable for decoding1937* by the PS Level 2 filter RunLengthDecode.1938* Array data to encode is inArr. Encoded data is written to outArr1939* outArr must be long enough to hold the encoded data but this1940* can't be known ahead of time.1941* A safe assumption is to use double the length of the input array.1942* This is then copied into a new array of the correct length which1943* is returned.1944* Algorithm:1945* Encoding is a lead byte followed by data bytes.1946* Lead byte of 0->127 indicates leadByte + 1 distinct bytes follow1947* Lead byte of 129->255 indicates 257 - leadByte is the number of times1948* the following byte is repeated in the source.1949* 128 is a special lead byte indicating end of data (EOD) and is1950* written as the final byte of the returned encoded data.1951*/1952private byte[] rlEncode(byte[] inArr) {19531954int inIndex = 0;1955int outIndex = 0;1956int startIndex = 0;1957int runLen = 0;1958byte[] outArr = new byte[(inArr.length * 2) +2];1959while (inIndex < inArr.length) {1960if (runLen == 0) {1961startIndex = inIndex++;1962runLen=1;1963}19641965while (runLen < 128 && inIndex < inArr.length &&1966inArr[inIndex] == inArr[startIndex]) {1967runLen++; // count run of same value1968inIndex++;1969}19701971if (runLen > 1) {1972outArr[outIndex++] = (byte)(257 - runLen);1973outArr[outIndex++] = inArr[startIndex];1974runLen = 0;1975continue; // back to top of while loop.1976}19771978// if reach here have a run of different values, or at the end.1979while (runLen < 128 && inIndex < inArr.length &&1980inArr[inIndex] != inArr[inIndex-1]) {1981runLen++; // count run of different values1982inIndex++;1983}1984outArr[outIndex++] = (byte)(runLen - 1);1985for (int i = startIndex; i < startIndex+runLen; i++) {1986outArr[outIndex++] = inArr[i];1987}1988runLen = 0;1989}1990outArr[outIndex++] = (byte)128;1991byte[] encodedData = new byte[outIndex];1992System.arraycopy(outArr, 0, encodedData, 0, outIndex);19931994return encodedData;1995}19961997/* written acc. to Adobe Spec. "Filtered Files: ASCIIEncode Filter",1998* "PS Language Reference Manual, 2nd edition: Section 3.13"1999*/2000private byte[] ascii85Encode(byte[] inArr) {2001byte[] outArr = new byte[((inArr.length+4) * 5 / 4) + 2];2002long p1 = 85;2003long p2 = p1*p1;2004long p3 = p1*p2;2005long p4 = p1*p3;2006byte pling = '!';20072008int i = 0;2009int olen = 0;2010long val, rem;20112012while (i+3 < inArr.length) {2013val = ((long)((inArr[i++]&0xff))<<24) +2014((long)((inArr[i++]&0xff))<<16) +2015((long)((inArr[i++]&0xff))<< 8) +2016((long)(inArr[i++]&0xff));2017if (val == 0) {2018outArr[olen++] = 'z';2019} else {2020rem = val;2021outArr[olen++] = (byte)(rem / p4 + pling); rem = rem % p4;2022outArr[olen++] = (byte)(rem / p3 + pling); rem = rem % p3;2023outArr[olen++] = (byte)(rem / p2 + pling); rem = rem % p2;2024outArr[olen++] = (byte)(rem / p1 + pling); rem = rem % p1;2025outArr[olen++] = (byte)(rem + pling);2026}2027}2028// input not a multiple of 4 bytes, write partial output.2029if (i < inArr.length) {2030int n = inArr.length - i; // n bytes remain to be written20312032val = 0;2033while (i < inArr.length) {2034val = (val << 8) + (inArr[i++]&0xff);2035}20362037int append = 4 - n;2038while (append-- > 0) {2039val = val << 8;2040}2041byte []c = new byte[5];2042rem = val;2043c[0] = (byte)(rem / p4 + pling); rem = rem % p4;2044c[1] = (byte)(rem / p3 + pling); rem = rem % p3;2045c[2] = (byte)(rem / p2 + pling); rem = rem % p2;2046c[3] = (byte)(rem / p1 + pling); rem = rem % p1;2047c[4] = (byte)(rem + pling);20482049for (int b = 0; b < n+1 ; b++) {2050outArr[olen++] = c[b];2051}2052}20532054// write EOD marker.2055outArr[olen++]='~'; outArr[olen++]='>';20562057/* The original intention was to insert a newline after every 78 bytes.2058* This was mainly intended for legibility but I decided against this2059* partially because of the (small) amount of extra space, and2060* partially because for line breaks either would have to hardwire2061* ascii 10 (newline) or calculate space in bytes to allocate for2062* the platform's newline byte sequence. Also need to be careful2063* about where its inserted:2064* Ascii 85 decoder ignores white space except for one special case:2065* you must ensure you do not split the EOD marker across lines.2066*/2067byte[] retArr = new byte[olen];2068System.arraycopy(outArr, 0, retArr, 0, olen);2069return retArr;20702071}20722073/**2074* PluginPrinter generates EPSF wrapped with a header and trailer2075* comment. This conforms to the new requirements of Mozilla 1.72076* and FireFox 1.5 and later. Earlier versions of these browsers2077* did not support plugin printing in the general sense (not just Java).2078* A notable limitation of these browsers is that they handle plugins2079* which would span page boundaries by scaling plugin content to fit on a2080* single page. This means white space is left at the bottom of the2081* previous page and its impossible to print these cases as they appear on2082* the web page. This is contrast to how the same browsers behave on2083* Windows where it renders as on-screen.2084* Cases where the content fits on a single page do work fine, and they2085* are the majority of cases.2086* The scaling that the browser specifies to make the plugin content fit2087* when it is larger than a single page can hold is non-uniform. It2088* scales the axis in which the content is too large just enough to2089* ensure it fits. For content which is extremely long this could lead2090* to noticeable distortion. However that is probably rare enough that2091* its not worth compensating for that here, but we can revisit that if2092* needed, and compensate by making the scale for the other axis the2093* same.2094*/2095public static class PluginPrinter implements Printable {20962097private EPSPrinter epsPrinter;2098private Component applet;2099private PrintStream stream;2100private String epsTitle;2101private int bx, by, bw, bh;2102private int width, height;21032104/**2105* This is called from the Java Plug-in to print an Applet's2106* contents as EPS to a postscript stream provided by the browser.2107* @param applet the applet component to print.2108* @param stream the print stream provided by the plug-in2109* @param x the x location of the applet panel in the browser window2110* @param y the y location of the applet panel in the browser window2111* @param w the width of the applet panel in the browser window2112* @param h the width of the applet panel in the browser window2113*/2114public PluginPrinter(Component applet,2115PrintStream stream,2116int x, int y, int w, int h) {21172118this.applet = applet;2119this.epsTitle = "Java Plugin Applet";2120this.stream = stream;2121bx = x;2122by = y;2123bw = w;2124bh = h;2125width = applet.size().width;2126height = applet.size().height;2127epsPrinter = new EPSPrinter(this, epsTitle, stream,21280, 0, width, height);2129}21302131public void printPluginPSHeader() {2132stream.println("%%BeginDocument: JavaPluginApplet");2133}21342135public void printPluginApplet() {2136try {2137epsPrinter.print();2138} catch (PrinterException e) {2139}2140}21412142public void printPluginPSTrailer() {2143stream.println("%%EndDocument: JavaPluginApplet");2144stream.flush();2145}21462147public void printAll() {2148printPluginPSHeader();2149printPluginApplet();2150printPluginPSTrailer();2151}21522153public int print(Graphics g, PageFormat pf, int pgIndex) {2154if (pgIndex > 0) {2155return Printable.NO_SUCH_PAGE;2156} else {2157// "aware" client code can detect that its been passed a2158// PrinterGraphics and could theoretically print2159// differently. I think this is more likely useful than2160// a problem.2161applet.printAll(g);2162return Printable.PAGE_EXISTS;2163}2164}21652166}21672168/*2169* This class can take an application-client supplied printable object2170* and send the result to a stream.2171* The application does not need to send any postscript to this stream2172* unless it needs to specify a translation etc.2173* It assumes that its importing application obeys all the conventions2174* for importation of EPS. See Appendix H - Encapsulated Postscript File2175* Format - of the Adobe Postscript Language Reference Manual, 2nd edition.2176* This class could be used as the basis for exposing the ability to2177* generate EPSF from 2D graphics as a StreamPrintService.2178* In that case a MediaPrintableArea attribute could be used to2179* communicate the bounding box.2180*/2181public static class EPSPrinter implements Pageable {21822183private PageFormat pf;2184private PSPrinterJob job;2185private int llx, lly, urx, ury;2186private Printable printable;2187private PrintStream stream;2188private String epsTitle;21892190public EPSPrinter(Printable printable, String title,2191PrintStream stream,2192int x, int y, int wid, int hgt) {21932194this.printable = printable;2195this.epsTitle = title;2196this.stream = stream;2197llx = x;2198lly = y;2199urx = llx+wid;2200ury = lly+hgt;2201// construct a PageFormat with zero margins representing the2202// exact bounds of the applet. ie construct a theoretical2203// paper which happens to exactly match applet panel size.2204Paper p = new Paper();2205p.setSize((double)wid, (double)hgt);2206p.setImageableArea(0.0,0.0, (double)wid, (double)hgt);2207pf = new PageFormat();2208pf.setPaper(p);2209}22102211public void print() throws PrinterException {2212stream.println("%!PS-Adobe-3.0 EPSF-3.0");2213stream.println("%%BoundingBox: " +2214llx + " " + lly + " " + urx + " " + ury);2215stream.println("%%Title: " + epsTitle);2216stream.println("%%Creator: Java Printing");2217stream.println("%%CreationDate: " + new java.util.Date());2218stream.println("%%EndComments");2219stream.println("/pluginSave save def");2220stream.println("mark"); // for restoring stack state on return22212222job = new PSPrinterJob();2223job.epsPrinter = this; // modifies the behaviour of PSPrinterJob2224job.mPSStream = stream;2225job.mDestType = RasterPrinterJob.STREAM; // prevents closure22262227job.startDoc();2228try {2229job.printPage(this, 0);2230} catch (Throwable t) {2231if (t instanceof PrinterException) {2232throw (PrinterException)t;2233} else {2234throw new PrinterException(t.toString());2235}2236} finally {2237stream.println("cleartomark"); // restore stack state2238stream.println("pluginSave restore");2239job.endDoc();2240}2241stream.flush();2242}22432244public int getNumberOfPages() {2245return 1;2246}22472248public PageFormat getPageFormat(int pgIndex) {2249if (pgIndex > 0) {2250throw new IndexOutOfBoundsException("pgIndex");2251} else {2252return pf;2253}2254}22552256public Printable getPrintable(int pgIndex) {2257if (pgIndex > 0) {2258throw new IndexOutOfBoundsException("pgIndex");2259} else {2260return printable;2261}2262}22632264}2265}226622672268