Path: blob/master/src/java.desktop/share/classes/sun/print/PSPrinterJob.java
41153 views
/*1* Copyright (c) 1998, 2021, 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.Shape;3637import java.awt.font.FontRenderContext;3839import java.awt.geom.AffineTransform;40import java.awt.geom.PathIterator;41import java.awt.geom.Rectangle2D;4243import java.awt.image.BufferedImage;4445import java.awt.print.Pageable;46import java.awt.print.PageFormat;47import java.awt.print.Paper;48import java.awt.print.Printable;49import java.awt.print.PrinterException;50import java.awt.print.PrinterIOException;51import java.awt.print.PrinterJob;5253import javax.print.PrintService;54import javax.print.StreamPrintService;55import javax.print.attribute.HashPrintRequestAttributeSet;56import javax.print.attribute.PrintRequestAttributeSet;57import javax.print.attribute.PrintServiceAttributeSet;58import javax.print.attribute.standard.PrinterName;59import javax.print.attribute.standard.Copies;60import javax.print.attribute.standard.Destination;61import javax.print.attribute.standard.DialogTypeSelection;62import javax.print.attribute.standard.JobName;63import javax.print.attribute.standard.Sides;6465import java.io.BufferedInputStream;66import java.io.BufferedOutputStream;67import java.io.BufferedReader;68import java.io.File;69import java.io.InputStream;70import java.io.InputStreamReader;71import java.io.IOException;72import java.io.FileInputStream;73import java.io.FileOutputStream;74import java.io.OutputStream;75import java.io.PrintStream;76import java.io.PrintWriter;77import java.io.StringWriter;7879import java.util.ArrayList;80import java.util.Locale;81import java.util.Properties;8283import sun.awt.CharsetString;84import sun.awt.FontConfiguration;85import sun.awt.PlatformFont;86import sun.awt.SunToolkit;87import sun.font.FontAccess;88import sun.font.FontUtilities;8990import java.nio.charset.*;91import java.nio.CharBuffer;92import java.nio.ByteBuffer;93import java.nio.file.Files;9495//REMIND: Remove use of this class when IPPPrintService is moved to share directory.96import java.lang.reflect.Method;97import javax.print.attribute.Attribute;98import javax.print.attribute.standard.JobSheets;99import javax.print.attribute.standard.Media;100101/**102* A class which initiates and executes a PostScript printer job.103*104* @author Richard Blanchard105*/106public class PSPrinterJob extends RasterPrinterJob {107108/* Class Constants */109110/**111* Passed to the {@code setFillMode}112* method this value forces fills to be113* done using the even-odd fill rule.114*/115protected static final int FILL_EVEN_ODD = 1;116117/**118* Passed to the {@code setFillMode}119* method this value forces fills to be120* done using the non-zero winding rule.121*/122protected static final int FILL_WINDING = 2;123124/* PostScript has a 64K maximum on its strings.125*/126private static final int MAX_PSSTR = (1024 * 64 - 1);127128private static final int RED_MASK = 0x00ff0000;129private static final int GREEN_MASK = 0x0000ff00;130private static final int BLUE_MASK = 0x000000ff;131132private static final int RED_SHIFT = 16;133private static final int GREEN_SHIFT = 8;134private static final int BLUE_SHIFT = 0;135136private static final int LOWNIBBLE_MASK = 0x0000000f;137private static final int HINIBBLE_MASK = 0x000000f0;138private static final int HINIBBLE_SHIFT = 4;139private static final byte[] hexDigits = {140(byte)'0', (byte)'1', (byte)'2', (byte)'3',141(byte)'4', (byte)'5', (byte)'6', (byte)'7',142(byte)'8', (byte)'9', (byte)'A', (byte)'B',143(byte)'C', (byte)'D', (byte)'E', (byte)'F'144};145146private static final int PS_XRES = 300;147private static final int PS_YRES = 300;148149private static final String ADOBE_PS_STR = "%!PS-Adobe-3.0";150private static final String EOF_COMMENT = "%%EOF";151private static final String PAGE_COMMENT = "%%Page: ";152153private static final String READIMAGEPROC = "/imStr 0 def /imageSrc " +154"{currentfile /ASCII85Decode filter /RunLengthDecode filter " +155" imStr readstring pop } def";156157private static final String COPIES = "/#copies exch def";158private static final String PAGE_SAVE = "/pgSave save def";159private static final String PAGE_RESTORE = "pgSave restore";160private static final String SHOWPAGE = "showpage";161private static final String IMAGE_SAVE = "/imSave save def";162private static final String IMAGE_STR = " string /imStr exch def";163private static final String IMAGE_RESTORE = "imSave restore";164165private static final String SetFontName = "F";166167private static final String DrawStringName = "S";168169/**170* The PostScript invocation to fill a path using the171* even-odd rule. (eofill)172*/173private static final String EVEN_ODD_FILL_STR = "EF";174175/**176* The PostScript invocation to fill a path using the177* non-zero winding rule. (fill)178*/179private static final String WINDING_FILL_STR = "WF";180181/**182* The PostScript to set the clip to be the current path183* using the even odd rule. (eoclip)184*/185private static final String EVEN_ODD_CLIP_STR = "EC";186187/**188* The PostScript to set the clip to be the current path189* using the non-zero winding rule. (clip)190*/191private static final String WINDING_CLIP_STR = "WC";192193/**194* Expecting two numbers on the PostScript stack, this195* invocation moves the current pen position. (moveto)196*/197private static final String MOVETO_STR = " M";198/**199* Expecting two numbers on the PostScript stack, this200* invocation draws a PS line from the current pen201* position to the point on the stack. (lineto)202*/203private static final String LINETO_STR = " L";204205/**206* This PostScript operator takes two control points207* and an ending point and using the current pen208* position as a starting point adds a bezier209* curve to the current path. (curveto)210*/211private static final String CURVETO_STR = " C";212213/**214* The PostScript to pop a state off of the printer's215* gstate stack. (grestore)216*/217private static final String GRESTORE_STR = "R";218/**219* The PostScript to push a state on to the printer's220* gstate stack. (gsave)221*/222private static final String GSAVE_STR = "G";223224/**225* Make the current PostScript path an empty path. (newpath)226*/227private static final String NEWPATH_STR = "N";228229/**230* Close the current subpath by generating a line segment231* from the current position to the start of the subpath. (closepath)232*/233private static final String CLOSEPATH_STR = "P";234235/**236* Use the three numbers on top of the PS operator237* stack to set the rgb color. (setrgbcolor)238*/239private static final String SETRGBCOLOR_STR = " SC";240241/**242* Use the top number on the stack to set the printer's243* current gray value. (setgray)244*/245private static final String SETGRAY_STR = " SG";246247/* Instance Variables */248249private int mDestType;250251private String mDestination = "lp";252253private boolean mNoJobSheet = false;254255private String mOptions;256257private Font mLastFont;258259private Color mLastColor;260261private Shape mLastClip;262263private AffineTransform mLastTransform;264265private double xres = PS_XRES;266private double yres = PS_XRES;267268/* non-null if printing EPS for Java Plugin */269private EPSPrinter epsPrinter = null;270271/**272* The metrics for the font currently set.273*/274FontMetrics mCurMetrics;275276/**277* The output stream to which the generated PostScript278* is written.279*/280PrintStream mPSStream;281282/* The temporary file to which we spool before sending to the printer */283284File spoolFile;285286/**287* This string holds the PostScript operator to288* be used to fill a path. It can be changed289* by the {@code setFillMode} method.290*/291private String mFillOpStr = WINDING_FILL_STR;292293/**294* This string holds the PostScript operator to295* be used to clip to a path. It can be changed296* by the {@code setFillMode} method.297*/298private String mClipOpStr = WINDING_CLIP_STR;299300/**301* A stack that represents the PostScript gstate stack.302*/303ArrayList<GState> mGStateStack = new ArrayList<>();304305/**306* The x coordinate of the current pen position.307*/308private float mPenX;309310/**311* The y coordinate of the current pen position.312*/313private float mPenY;314315/**316* The x coordinate of the starting point of317* the current subpath.318*/319private float mStartPathX;320321/**322* The y coordinate of the starting point of323* the current subpath.324*/325private float mStartPathY;326327/**328* An optional mapping of fonts to PostScript names.329*/330private static Properties mFontProps = null;331332private static boolean isMac;333334/* Class static initialiser block */335static {336initStatic();337}338339@SuppressWarnings("removal")340private static void initStatic() {341//enable priviledges so initProps can access system properties,342// open the property file, etc.343java.security.AccessController.doPrivileged(344new java.security.PrivilegedAction<Object>() {345public Object run() {346mFontProps = initProps();347String osName = System.getProperty("os.name");348isMac = osName.startsWith("Mac");349return null;350}351});352}353354/*355* Initialize PostScript font properties.356* Copied from PSPrintStream357*/358private static Properties initProps() {359// search psfont.properties for fonts360// and create and initialize fontProps if it exist.361362String jhome = System.getProperty("java.home");363364if (jhome != null){365String ulocale = SunToolkit.getStartupLocale().getLanguage();366try {367368File f = new File(jhome + File.separator +369"lib" + File.separator +370"psfontj2d.properties." + ulocale);371372if (!f.canRead()){373374f = new File(jhome + File.separator +375"lib" + File.separator +376"psfont.properties." + ulocale);377if (!f.canRead()){378379f = new File(jhome + File.separator + "lib" +380File.separator + "psfontj2d.properties");381382if (!f.canRead()){383384f = new File(jhome + File.separator + "lib" +385File.separator + "psfont.properties");386387if (!f.canRead()){388return (Properties)null;389}390}391}392}393394// Load property file395InputStream in =396new BufferedInputStream(new FileInputStream(f.getPath()));397Properties props = new Properties();398props.load(in);399in.close();400return props;401} catch (Exception e){402return (Properties)null;403}404}405return (Properties)null;406}407408/* Constructors */409410public PSPrinterJob()411{412}413414/* Instance Methods */415416/**417* Presents the user a dialog for changing properties of the418* print job interactively.419* @return false if the user cancels the dialog and420* true otherwise.421* @exception HeadlessException if GraphicsEnvironment.isHeadless()422* returns true.423* @see java.awt.GraphicsEnvironment#isHeadless424*/425public boolean printDialog() throws HeadlessException {426427if (GraphicsEnvironment.isHeadless()) {428throw new HeadlessException();429}430431if (attributes == null) {432attributes = new HashPrintRequestAttributeSet();433}434attributes.add(new Copies(getCopies()));435attributes.add(new JobName(getJobName(), null));436437boolean doPrint = false;438DialogTypeSelection dts =439(DialogTypeSelection)attributes.get(DialogTypeSelection.class);440if (dts == DialogTypeSelection.NATIVE) {441// Remove DialogTypeSelection.NATIVE to prevent infinite loop in442// RasterPrinterJob.443attributes.remove(DialogTypeSelection.class);444doPrint = printDialog(attributes);445// restore attribute446attributes.add(DialogTypeSelection.NATIVE);447} else {448doPrint = printDialog(attributes);449}450451if (doPrint) {452JobName jobName = (JobName)attributes.get(JobName.class);453if (jobName != null) {454setJobName(jobName.getValue());455}456Copies copies = (Copies)attributes.get(Copies.class);457if (copies != null) {458setCopies(copies.getValue());459}460461Destination dest = (Destination)attributes.get(Destination.class);462463if (dest != null) {464try {465mDestType = RasterPrinterJob.FILE;466mDestination = (new File(dest.getURI())).getPath();467} catch (Exception e) {468mDestination = "out.ps";469}470} else {471mDestType = RasterPrinterJob.PRINTER;472PrintService pServ = getPrintService();473if (pServ != null) {474mDestination = pServ.getName();475if (isMac) {476PrintServiceAttributeSet psaSet = pServ.getAttributes() ;477if (psaSet != null) {478mDestination = psaSet.get(PrinterName.class).toString();479}480}481}482}483}484485return doPrint;486}487488@Override489protected void setAttributes(PrintRequestAttributeSet attributes)490throws PrinterException {491super.setAttributes(attributes);492if (attributes == null) {493return; // now always use attributes, so this shouldn't happen.494}495Attribute attr = attributes.get(Media.class);496if (attr instanceof CustomMediaTray) {497CustomMediaTray customTray = (CustomMediaTray)attr;498String choice = customTray.getChoiceName();499if (choice != null) {500mOptions = " InputSlot="+ choice;501}502}503}504505/**506* Invoked by the RasterPrinterJob super class507* this method is called to mark the start of a508* document.509*/510@SuppressWarnings("removal")511protected void startDoc() throws PrinterException {512513// A security check has been performed in the514// java.awt.print.printerJob.getPrinterJob method.515// We use an inner class to execute the privilged open operations.516// Note that we only open a file if it has been nominated by517// the end-user in a dialog that we ouselves put up.518519OutputStream output = null;520521if (epsPrinter == null) {522if (getPrintService() instanceof PSStreamPrintService) {523StreamPrintService sps = (StreamPrintService)getPrintService();524mDestType = RasterPrinterJob.STREAM;525if (sps.isDisposed()) {526throw new PrinterException("service is disposed");527}528output = sps.getOutputStream();529if (output == null) {530throw new PrinterException("Null output stream");531}532} else {533/* REMIND: This needs to be more maintainable */534mNoJobSheet = super.noJobSheet;535if (super.destinationAttr != null) {536mDestType = RasterPrinterJob.FILE;537mDestination = super.destinationAttr;538}539if (mDestType == RasterPrinterJob.FILE) {540try {541spoolFile = new File(mDestination);542output = new FileOutputStream(spoolFile);543} catch (IOException ex) {544abortDoc();545throw new PrinterIOException(ex);546}547} else {548PrinterOpener po = new PrinterOpener();549java.security.AccessController.doPrivileged(po);550if (po.pex != null) {551throw po.pex;552}553output = po.result;554}555}556557mPSStream = new PrintStream(new BufferedOutputStream(output));558mPSStream.println(ADOBE_PS_STR);559}560561mPSStream.println("%%BeginProlog");562mPSStream.println(READIMAGEPROC);563mPSStream.println("/BD {bind def} bind def");564mPSStream.println("/D {def} BD");565mPSStream.println("/C {curveto} BD");566mPSStream.println("/L {lineto} BD");567mPSStream.println("/M {moveto} BD");568mPSStream.println("/R {grestore} BD");569mPSStream.println("/G {gsave} BD");570mPSStream.println("/N {newpath} BD");571mPSStream.println("/P {closepath} BD");572mPSStream.println("/EC {eoclip} BD");573mPSStream.println("/WC {clip} BD");574mPSStream.println("/EF {eofill} BD");575mPSStream.println("/WF {fill} BD");576mPSStream.println("/SG {setgray} BD");577mPSStream.println("/SC {setrgbcolor} BD");578mPSStream.println("/ISOF {");579mPSStream.println(" dup findfont dup length 1 add dict begin {");580mPSStream.println(" 1 index /FID eq {pop pop} {D} ifelse");581mPSStream.println(" } forall /Encoding ISOLatin1Encoding D");582mPSStream.println(" currentdict end definefont");583mPSStream.println("} BD");584mPSStream.println("/NZ {dup 1 lt {pop 1} if} BD");585/* The following procedure takes args: string, x, y, desiredWidth.586* It calculates using stringwidth the width of the string in the587* current font and subtracts it from the desiredWidth and divides588* this by stringLen-1. This gives us a per-glyph adjustment in589* the spacing needed (either +ve or -ve) to make the string590* print at the desiredWidth. The ashow procedure call takes this591* per-glyph adjustment as an argument. This is necessary for WYSIWYG592*/593mPSStream.println("/"+DrawStringName +" {");594mPSStream.println(" moveto 1 index stringwidth pop NZ sub");595mPSStream.println(" 1 index length 1 sub NZ div 0");596mPSStream.println(" 3 2 roll ashow newpath} BD");597mPSStream.println("/FL [");598if (mFontProps == null){599mPSStream.println(" /Helvetica ISOF");600mPSStream.println(" /Helvetica-Bold ISOF");601mPSStream.println(" /Helvetica-Oblique ISOF");602mPSStream.println(" /Helvetica-BoldOblique ISOF");603mPSStream.println(" /Times-Roman ISOF");604mPSStream.println(" /Times-Bold ISOF");605mPSStream.println(" /Times-Italic ISOF");606mPSStream.println(" /Times-BoldItalic ISOF");607mPSStream.println(" /Courier ISOF");608mPSStream.println(" /Courier-Bold ISOF");609mPSStream.println(" /Courier-Oblique ISOF");610mPSStream.println(" /Courier-BoldOblique ISOF");611} else {612int cnt = Integer.parseInt(mFontProps.getProperty("font.num", "9"));613for (int i = 0; i < cnt; i++){614mPSStream.println(" /" + mFontProps.getProperty615("font." + String.valueOf(i), "Courier ISOF"));616}617}618mPSStream.println("] D");619620mPSStream.println("/"+SetFontName +" {");621mPSStream.println(" FL exch get exch scalefont");622mPSStream.println(" [1 0 0 -1 0 0] makefont setfont} BD");623624mPSStream.println("%%EndProlog");625626mPSStream.println("%%BeginSetup");627if (epsPrinter == null) {628// Set Page Size using first page's format.629PageFormat pageFormat = getPageable().getPageFormat(0);630double paperHeight = pageFormat.getPaper().getHeight();631double paperWidth = pageFormat.getPaper().getWidth();632633/* PostScript printers can always generate uncollated copies.634*/635mPSStream.print("<< /PageSize [" +636paperWidth + " "+ paperHeight+"]");637638final PrintService pservice = getPrintService();639Boolean isPS = java.security.AccessController.doPrivileged(640new java.security.PrivilegedAction<Boolean>() {641public Boolean run() {642try {643Class<?> psClass = Class.forName("sun.print.IPPPrintService");644if (psClass.isInstance(pservice)) {645Method isPSMethod = psClass.getMethod("isPostscript",646(Class[])null);647return (Boolean)isPSMethod.invoke(pservice, (Object[])null);648}649} catch (Throwable t) {650}651return Boolean.TRUE;652}653}654);655if (isPS) {656mPSStream.print(" /DeferredMediaSelection true");657}658659mPSStream.print(" /ImagingBBox null /ManualFeed false");660mPSStream.print(isCollated() ? " /Collate true":"");661mPSStream.print(" /NumCopies " +getCopiesInt());662663if (sidesAttr != Sides.ONE_SIDED) {664if (sidesAttr == Sides.TWO_SIDED_LONG_EDGE) {665mPSStream.print(" /Duplex true ");666} else if (sidesAttr == Sides.TWO_SIDED_SHORT_EDGE) {667mPSStream.print(" /Duplex true /Tumble true ");668}669}670mPSStream.println(" >> setpagedevice ");671}672mPSStream.println("%%EndSetup");673}674675// Inner class to run "privileged" to open the printer output stream.676677private class PrinterOpener implements java.security.PrivilegedAction<OutputStream> {678PrinterException pex;679OutputStream result;680681public OutputStream run() {682try {683684/* Write to a temporary file which will be spooled to685* the printer then deleted. In the case that the file686* is not removed for some reason, request that it is687* removed when the VM exits.688*/689spoolFile = Files.createTempFile("javaprint", ".ps").toFile();690spoolFile.deleteOnExit();691692result = new FileOutputStream(spoolFile);693return result;694} catch (IOException ex) {695// If there is an IOError we subvert it to a PrinterException.696pex = new PrinterIOException(ex);697}698return null;699}700}701702// Inner class to run "privileged" to invoke the system print command703704private class PrinterSpooler implements java.security.PrivilegedAction<Object> {705PrinterException pex;706707private void handleProcessFailure(final Process failedProcess,708final String[] execCmd, final int result) throws IOException {709try (StringWriter sw = new StringWriter();710PrintWriter pw = new PrintWriter(sw)) {711pw.append("error=").append(Integer.toString(result));712pw.append(" running:");713for (String arg: execCmd) {714pw.append(" '").append(arg).append("'");715}716try (InputStream is = failedProcess.getErrorStream();717InputStreamReader isr = new InputStreamReader(is);718BufferedReader br = new BufferedReader(isr)) {719while (br.ready()) {720pw.println();721pw.append("\t\t").append(br.readLine());722}723} finally {724pw.flush();725}726throw new IOException(sw.toString());727}728}729730public Object run() {731if (spoolFile == null || !spoolFile.exists()) {732pex = new PrinterException("No spool file");733return null;734}735try {736/**737* Spool to the printer.738*/739String fileName = spoolFile.getAbsolutePath();740String[] execCmd = printExecCmd(mDestination, mOptions,741mNoJobSheet, getJobNameInt(),7421, fileName);743744Process process = Runtime.getRuntime().exec(execCmd);745process.waitFor();746final int result = process.exitValue();747if (0 != result) {748handleProcessFailure(process, execCmd, result);749}750} catch (IOException ex) {751pex = new PrinterIOException(ex);752} catch (InterruptedException ie) {753pex = new PrinterException(ie.toString());754} finally {755spoolFile.delete();756}757return null;758}759}760761762/**763* Invoked if the application cancelled the printjob.764*/765@SuppressWarnings("removal")766protected void abortDoc() {767if (mPSStream != null && mDestType != RasterPrinterJob.STREAM) {768mPSStream.close();769}770java.security.AccessController.doPrivileged(771new java.security.PrivilegedAction<Object>() {772773public Object run() {774if (spoolFile != null && spoolFile.exists()) {775spoolFile.delete();776}777return null;778}779});780}781782/**783* Invoked by the RasterPrintJob super class784* this method is called after that last page785* has been imaged.786*/787@SuppressWarnings("removal")788protected void endDoc() throws PrinterException {789if (mPSStream != null) {790mPSStream.println(EOF_COMMENT);791mPSStream.flush();792if (mPSStream.checkError()) {793abortDoc();794throw new PrinterException("Error while writing to file");795}796if (mDestType != RasterPrinterJob.STREAM) {797mPSStream.close();798}799}800if (mDestType == RasterPrinterJob.PRINTER) {801PrintService pServ = getPrintService();802if (pServ != null) {803mDestination = pServ.getName();804if (isMac) {805PrintServiceAttributeSet psaSet = pServ.getAttributes();806if (psaSet != null) {807mDestination = psaSet.get(PrinterName.class).toString() ;808}809}810}811PrinterSpooler spooler = new PrinterSpooler();812java.security.AccessController.doPrivileged(spooler);813if (spooler.pex != null) {814throw spooler.pex;815}816}817}818819private String getCoordPrep() {820return " 0 exch translate "821+ "1 -1 scale"822+ "[72 " + getXRes() + " div "823+ "0 0 "824+ "72 " + getYRes() + " div "825+ "0 0]concat";826}827828/**829* The RasterPrintJob super class calls this method830* at the start of each page.831*/832protected void startPage(PageFormat pageFormat, Printable painter,833int index, boolean paperChanged)834throws PrinterException835{836double paperHeight = pageFormat.getPaper().getHeight();837double paperWidth = pageFormat.getPaper().getWidth();838int pageNumber = index + 1;839840/* Place an initial gstate on to our gstate stack.841* It will have the default PostScript gstate842* attributes.843*/844mGStateStack = new ArrayList<>();845mGStateStack.add(new GState());846847mPSStream.println(PAGE_COMMENT + pageNumber + " " + pageNumber);848849/* Check current page's pageFormat against the previous pageFormat,850*/851if (index > 0 && paperChanged) {852853mPSStream.print("<< /PageSize [" +854paperWidth + " " + paperHeight + "]");855856final PrintService pservice = getPrintService();857@SuppressWarnings("removal")858Boolean isPS = java.security.AccessController.doPrivileged(859new java.security.PrivilegedAction<Boolean>() {860public Boolean run() {861try {862Class<?> psClass =863Class.forName("sun.print.IPPPrintService");864if (psClass.isInstance(pservice)) {865Method isPSMethod =866psClass.getMethod("isPostscript",867(Class[])null);868return (Boolean)869isPSMethod.invoke(pservice,870(Object[])null);871}872} catch (Throwable t) {873}874return Boolean.TRUE;875}876}877);878879if (isPS) {880mPSStream.print(" /DeferredMediaSelection true");881}882mPSStream.println(" >> setpagedevice");883}884mPSStream.println(PAGE_SAVE);885mPSStream.println(paperHeight + getCoordPrep());886}887888/**889* The RastePrintJob super class calls this method890* at the end of each page.891*/892protected void endPage(PageFormat format, Printable painter,893int index)894throws PrinterException895{896mPSStream.println(PAGE_RESTORE);897mPSStream.println(SHOWPAGE);898}899900/**901* Convert the 24 bit BGR image buffer represented by902* {@code image} to PostScript. The image is drawn at903* {@code (destX, destY)} in device coordinates.904* The image is scaled into a square of size905* specified by {@code destWidth} and906* {@code destHeight}. The portion of the907* source image copied into that square is specified908* by {@code srcX}, {@code srcY},909* {@code srcWidth}, and srcHeight.910*/911protected void drawImageBGR(byte[] bgrData,912float destX, float destY,913float destWidth, float destHeight,914float srcX, float srcY,915float srcWidth, float srcHeight,916int srcBitMapWidth, int srcBitMapHeight) {917918/* We draw images at device resolution so we probably need919* to change the current PostScript transform.920*/921setTransform(new AffineTransform());922prepDrawing();923924int intSrcWidth = (int) srcWidth;925int intSrcHeight = (int) srcHeight;926927mPSStream.println(IMAGE_SAVE);928929/* Create a PS string big enough to hold a row of pixels.930*/931int psBytesPerRow = 3 * intSrcWidth;932while (psBytesPerRow > MAX_PSSTR) {933psBytesPerRow /= 2;934}935936mPSStream.println(psBytesPerRow + IMAGE_STR);937938/* Scale and translate the unit image.939*/940mPSStream.println("[" + destWidth + " 0 "941+ "0 " + destHeight942+ " " + destX + " " + destY943+"]concat");944945/* Color Image invocation.946*/947mPSStream.println(intSrcWidth + " " + intSrcHeight + " " + 8 + "["948+ intSrcWidth + " 0 "949+ "0 " + intSrcHeight950+ " 0 " + 0 + "]"951+ "/imageSrc load false 3 colorimage");952953/* Image data.954*/955int index = 0;956byte[] rgbData = new byte[intSrcWidth * 3];957958try {959/* Skip the parts of the image that are not part960* of the source rectangle.961*/962index = (int) srcY * srcBitMapWidth;963964for(int i = 0; i < intSrcHeight; i++) {965966/* Skip the left part of the image that is not967* part of the source rectangle.968*/969index += (int) srcX;970971index = swapBGRtoRGB(bgrData, index, rgbData);972byte[] encodedData = rlEncode(rgbData);973byte[] asciiData = ascii85Encode(encodedData);974mPSStream.write(asciiData);975mPSStream.println("");976}977978/*979* If there is an IOError we subvert it to a PrinterException.980* Fix: There has got to be a better way, maybe define981* a PrinterIOException and then throw that?982*/983} catch (IOException e) {984//throw new PrinterException(e.toString());985}986987mPSStream.println(IMAGE_RESTORE);988}989990/**991* Prints the contents of the array of ints, 'data'992* to the current page. The band is placed at the993* location (x, y) in device coordinates on the994* page. The width and height of the band is995* specified by the caller. Currently the data996* is 24 bits per pixel in BGR format.997*/998protected void printBand(byte[] bgrData, int x, int y,999int width, int height)1000throws PrinterException1001{10021003mPSStream.println(IMAGE_SAVE);10041005/* Create a PS string big enough to hold a row of pixels.1006*/1007int psBytesPerRow = 3 * width;1008while (psBytesPerRow > MAX_PSSTR) {1009psBytesPerRow /= 2;1010}10111012mPSStream.println(psBytesPerRow + IMAGE_STR);10131014/* Scale and translate the unit image.1015*/1016mPSStream.println("[" + width + " 0 "1017+ "0 " + height1018+ " " + x + " " + y1019+"]concat");10201021/* Color Image invocation.1022*/1023mPSStream.println(width + " " + height + " " + 8 + "["1024+ width + " 0 "1025+ "0 " + -height1026+ " 0 " + height + "]"1027+ "/imageSrc load false 3 colorimage");10281029/* Image data.1030*/1031int index = 0;1032byte[] rgbData = new byte[width*3];10331034try {1035for(int i = 0; i < height; i++) {1036index = swapBGRtoRGB(bgrData, index, rgbData);1037byte[] encodedData = rlEncode(rgbData);1038byte[] asciiData = ascii85Encode(encodedData);1039mPSStream.write(asciiData);1040mPSStream.println("");1041}10421043} catch (IOException e) {1044throw new PrinterIOException(e);1045}10461047mPSStream.println(IMAGE_RESTORE);1048}10491050/**1051* Examine the metrics captured by the1052* {@code PeekGraphics} instance and1053* if capable of directly converting this1054* print job to the printer's control language1055* or the native OS's graphics primitives, then1056* return a {@code PSPathGraphics} to perform1057* that conversion. If there is not an object1058* capable of the conversion then return1059* {@code null}. Returning {@code null}1060* causes the print job to be rasterized.1061*/10621063protected Graphics2D createPathGraphics(PeekGraphics peekGraphics,1064PrinterJob printerJob,1065Printable painter,1066PageFormat pageFormat,1067int pageIndex) {10681069PSPathGraphics pathGraphics;1070PeekMetrics metrics = peekGraphics.getMetrics();10711072/* If the application has drawn anything that1073* out PathGraphics class can not handle then1074* return a null PathGraphics.1075*/1076if (forcePDL == false && (forceRaster == true1077|| metrics.hasNonSolidColors()1078|| metrics.hasCompositing())) {10791080pathGraphics = null;1081} else {10821083BufferedImage bufferedImage = new BufferedImage(8, 8,1084BufferedImage.TYPE_INT_RGB);1085Graphics2D bufferedGraphics = bufferedImage.createGraphics();1086boolean canRedraw = peekGraphics.getAWTDrawingOnly() == false;10871088pathGraphics = new PSPathGraphics(bufferedGraphics, printerJob,1089painter, pageFormat, pageIndex,1090canRedraw);1091}10921093return pathGraphics;1094}10951096/**1097* Intersect the gstate's current path with the1098* current clip and make the result the new clip.1099*/1100protected void selectClipPath() {11011102mPSStream.println(mClipOpStr);1103}11041105protected void setClip(Shape clip) {11061107mLastClip = clip;1108}11091110protected void setTransform(AffineTransform transform) {1111mLastTransform = transform;1112}11131114/**1115* Set the current PostScript font.1116* Taken from outFont in PSPrintStream.1117*/1118protected boolean setFont(Font font) {1119mLastFont = font;1120return true;1121}11221123/**1124* Given an array of CharsetStrings that make up a run1125* of text, this routine converts each CharsetString to1126* an index into our PostScript font list. If one or more1127* CharsetStrings can not be represented by a PostScript1128* font, then this routine will return a null array.1129*/1130private int[] getPSFontIndexArray(Font font, CharsetString[] charSet) {1131int[] psFont = null;11321133if (mFontProps != null) {1134psFont = new int[charSet.length];1135}11361137for (int i = 0; i < charSet.length && psFont != null; i++){11381139/* Get the encoding of the run of text.1140*/1141CharsetString cs = charSet[i];11421143CharsetEncoder fontCS = cs.fontDescriptor.encoder;1144String charsetName = cs.fontDescriptor.getFontCharsetName();1145/*1146* sun.awt.Symbol perhaps should return "symbol" for encoding.1147* Similarly X11Dingbats should return "dingbats"1148* Forced to check for win32 & x/unix names for these converters.1149*/11501151if ("Symbol".equals(charsetName)) {1152charsetName = "symbol";1153} else if ("WingDings".equals(charsetName) ||1154"X11Dingbats".equals(charsetName)) {1155charsetName = "dingbats";1156} else {1157charsetName = makeCharsetName(charsetName, cs.charsetChars);1158}11591160int styleMask = font.getStyle() |1161FontUtilities.getFont2D(font).getStyle();11621163String style = FontConfiguration.getStyleString(styleMask);11641165/* First we map the font name through the properties file.1166* This mapping provides alias names for fonts, for example,1167* "timesroman" is mapped to "serif".1168*/1169String fontName = font.getFamily().toLowerCase(Locale.ENGLISH);1170fontName = fontName.replace(' ', '_');1171String name = mFontProps.getProperty(fontName, "");11721173/* Now map the alias name, character set name, and style1174* to a PostScript name.1175*/1176String psName =1177mFontProps.getProperty(name + "." + charsetName + "." + style,1178null);11791180if (psName != null) {11811182/* Get the PostScript font index for the PostScript font.1183*/1184try {1185psFont[i] =1186Integer.parseInt(mFontProps.getProperty(psName));11871188/* If there is no PostScript font for this font name,1189* then we want to termintate the loop and the method1190* indicating our failure. Setting the array to null1191* is used to indicate these failures.1192*/1193} catch(NumberFormatException e){1194psFont = null;1195}11961197/* There was no PostScript name for the font, character set,1198* and style so give up.1199*/1200} else {1201psFont = null;1202}1203}12041205return psFont;1206}120712081209private static String escapeParens(String str) {1210if (str.indexOf('(') == -1 && str.indexOf(')') == -1 ) {1211return str;1212} else {1213int count = 0;1214int pos = 0;1215while ((pos = str.indexOf('(', pos)) != -1) {1216count++;1217pos++;1218}1219pos = 0;1220while ((pos = str.indexOf(')', pos)) != -1) {1221count++;1222pos++;1223}1224char []inArr = str.toCharArray();1225char []outArr = new char[inArr.length+count];1226pos = 0;1227for (int i=0;i<inArr.length;i++) {1228if (inArr[i] == '(' || inArr[i] == ')') {1229outArr[pos++] = '\\';1230}1231outArr[pos++] = inArr[i];1232}1233return new String(outArr);12341235}1236}12371238/* return of 0 means unsupported. Other return indicates the number1239* of distinct PS fonts needed to draw this text. This saves us1240* doing this processing one extra time.1241*/1242protected int platformFontCount(Font font, String str) {1243if (mFontProps == null) {1244return 0;1245}1246PlatformFont peer = (PlatformFont) FontAccess.getFontAccess()1247.getFontPeer(font);1248CharsetString[] acs = peer.makeMultiCharsetString(str, false);1249if (acs == null) {1250/* AWT can't convert all chars so use 2D path */1251return 0;1252}1253int[] psFonts = getPSFontIndexArray(font, acs);1254return (psFonts == null) ? 0 : psFonts.length;1255}12561257protected boolean textOut(Graphics g, String str, float x, float y,1258Font mLastFont, FontRenderContext frc,1259float width) {1260boolean didText = true;12611262if (mFontProps == null) {1263return false;1264} else {1265prepDrawing();12661267/* On-screen drawString renders most control chars as the missing1268* glyph and have the non-zero advance of that glyph.1269* Exceptions are \t, \n and \r which are considered zero-width.1270* Postscript handles control chars mostly as a missing glyph.1271* But we use 'ashow' specifying a width for the string which1272* assumes zero-width for those three exceptions, and Postscript1273* tries to squeeze the extra char in, with the result that the1274* glyphs look compressed or even overlap.1275* So exclude those control chars from the string sent to PS.1276*/1277str = removeControlChars(str);1278if (str.length() == 0) {1279return true;1280}1281PlatformFont peer = (PlatformFont) FontAccess.getFontAccess()1282.getFontPeer(mLastFont);1283CharsetString[] acs = peer.makeMultiCharsetString(str, false);1284if (acs == null) {1285/* AWT can't convert all chars so use 2D path */1286return false;1287}1288/* Get an array of indices into our PostScript name1289* table. If all of the runs can not be converted1290* to PostScript fonts then null is returned and1291* we'll want to fall back to printing the text1292* as shapes.1293*/1294int[] psFonts = getPSFontIndexArray(mLastFont, acs);1295if (psFonts != null) {12961297for (int i = 0; i < acs.length; i++){1298CharsetString cs = acs[i];1299CharsetEncoder fontCS = cs.fontDescriptor.encoder;13001301StringBuilder nativeStr = new StringBuilder();1302byte[] strSeg = new byte[cs.length * 2];1303int len = 0;1304try {1305ByteBuffer bb = ByteBuffer.wrap(strSeg);1306fontCS.encode(CharBuffer.wrap(cs.charsetChars,1307cs.offset,1308cs.length),1309bb, true);1310bb.flip();1311len = bb.limit();1312} catch(IllegalStateException xx){1313continue;1314} catch(CoderMalfunctionError xx){1315continue;1316}1317/* The width to fit to may either be specified,1318* or calculated. Specifying by the caller is only1319* valid if the text does not need to be decomposed1320* into multiple calls.1321*/1322float desiredWidth;1323if (acs.length == 1 && width != 0f) {1324desiredWidth = width;1325} else {1326Rectangle2D r2d =1327mLastFont.getStringBounds(cs.charsetChars,1328cs.offset,1329cs.offset+cs.length,1330frc);1331desiredWidth = (float)r2d.getWidth();1332}1333/* unprintable chars had width of 0, causing a PS error1334*/1335if (desiredWidth == 0) {1336return didText;1337}1338nativeStr.append('<');1339for (int j = 0; j < len; j++){1340byte b = strSeg[j];1341// to avoid encoding conversion with println()1342String hexS = Integer.toHexString(b);1343int length = hexS.length();1344if (length > 2) {1345hexS = hexS.substring(length - 2, length);1346} else if (length == 1) {1347hexS = "0" + hexS;1348} else if (length == 0) {1349hexS = "00";1350}1351nativeStr.append(hexS);1352}1353nativeStr.append('>');1354/* This comment costs too much in output file size */1355// mPSStream.println("% Font[" + mLastFont.getName() + ", " +1356// FontConfiguration.getStyleString(mLastFont.getStyle()) + ", "1357// + mLastFont.getSize2D() + "]");1358getGState().emitPSFont(psFonts[i], mLastFont.getSize2D());13591360// out String1361mPSStream.println(nativeStr.toString() + " " +1362desiredWidth + " " + x + " " + y + " " +1363DrawStringName);1364x += desiredWidth;1365}1366} else {1367didText = false;1368}1369}13701371return didText;1372}1373/**1374* Set the current path rule to be either1375* {@code FILL_EVEN_ODD} (using the1376* even-odd file rule) or {@code FILL_WINDING}1377* (using the non-zero winding rule.)1378*/1379protected void setFillMode(int fillRule) {13801381switch (fillRule) {13821383case FILL_EVEN_ODD:1384mFillOpStr = EVEN_ODD_FILL_STR;1385mClipOpStr = EVEN_ODD_CLIP_STR;1386break;13871388case FILL_WINDING:1389mFillOpStr = WINDING_FILL_STR;1390mClipOpStr = WINDING_CLIP_STR;1391break;13921393default:1394throw new IllegalArgumentException();1395}13961397}13981399/**1400* Set the printer's current color to be that1401* defined by {@code color}1402*/1403protected void setColor(Color color) {1404mLastColor = color;1405}14061407/**1408* Fill the current path using the current fill mode1409* and color.1410*/1411protected void fillPath() {14121413mPSStream.println(mFillOpStr);1414}14151416/**1417* Called to mark the start of a new path.1418*/1419protected void beginPath() {14201421prepDrawing();1422mPSStream.println(NEWPATH_STR);14231424mPenX = 0;1425mPenY = 0;1426}14271428/**1429* Close the current subpath by appending a straight1430* line from the current point to the subpath's1431* starting point.1432*/1433protected void closeSubpath() {14341435mPSStream.println(CLOSEPATH_STR);14361437mPenX = mStartPathX;1438mPenY = mStartPathY;1439}144014411442/**1443* Generate PostScript to move the current pen1444* position to {@code (x, y)}.1445*/1446protected void moveTo(float x, float y) {14471448mPSStream.println(trunc(x) + " " + trunc(y) + MOVETO_STR);14491450/* moveto marks the start of a new subpath1451* and we need to remember that starting1452* position so that we know where the1453* pen returns to with a close path.1454*/1455mStartPathX = x;1456mStartPathY = y;14571458mPenX = x;1459mPenY = y;1460}1461/**1462* Generate PostScript to draw a line from the1463* current pen position to {@code (x, y)}.1464*/1465protected void lineTo(float x, float y) {14661467mPSStream.println(trunc(x) + " " + trunc(y) + LINETO_STR);14681469mPenX = x;1470mPenY = y;1471}14721473/**1474* Add to the current path a bezier curve formed1475* by the current pen position and the method parameters1476* which are two control points and an ending1477* point.1478*/1479protected void bezierTo(float control1x, float control1y,1480float control2x, float control2y,1481float endX, float endY) {14821483// mPSStream.println(control1x + " " + control1y1484// + " " + control2x + " " + control2y1485// + " " + endX + " " + endY1486// + CURVETO_STR);1487mPSStream.println(trunc(control1x) + " " + trunc(control1y)1488+ " " + trunc(control2x) + " " + trunc(control2y)1489+ " " + trunc(endX) + " " + trunc(endY)1490+ CURVETO_STR);149114921493mPenX = endX;1494mPenY = endY;1495}14961497String trunc(float f) {1498float af = Math.abs(f);1499if (af >= 1f && af <=1000f) {1500f = Math.round(f*1000)/1000f;1501}1502return Float.toString(f);1503}15041505/**1506* Return the x coordinate of the pen in the1507* current path.1508*/1509protected float getPenX() {15101511return mPenX;1512}1513/**1514* Return the y coordinate of the pen in the1515* current path.1516*/1517protected float getPenY() {15181519return mPenY;1520}15211522/**1523* Return the x resolution of the coordinates1524* to be rendered.1525*/1526protected double getXRes() {1527return xres;1528}1529/**1530* Return the y resolution of the coordinates1531* to be rendered.1532*/1533protected double getYRes() {1534return yres;1535}15361537/**1538* Set the resolution at which to print.1539*/1540protected void setXYRes(double x, double y) {1541xres = x;1542yres = y;1543}15441545/**1546* For PostScript the origin is in the upper-left of the1547* paper not at the imageable area corner.1548*/1549protected double getPhysicalPrintableX(Paper p) {1550return 0;15511552}15531554/**1555* For PostScript the origin is in the upper-left of the1556* paper not at the imageable area corner.1557*/1558protected double getPhysicalPrintableY(Paper p) {1559return 0;1560}15611562protected double getPhysicalPrintableWidth(Paper p) {1563return p.getImageableWidth();1564}15651566protected double getPhysicalPrintableHeight(Paper p) {1567return p.getImageableHeight();1568}15691570protected double getPhysicalPageWidth(Paper p) {1571return p.getWidth();1572}15731574protected double getPhysicalPageHeight(Paper p) {1575return p.getHeight();1576}15771578/**1579* Returns how many times each page in the book1580* should be consecutively printed by PrintJob.1581* If the printer makes copies itself then this1582* method should return 1.1583*/1584protected int getNoncollatedCopies() {1585return 1;1586}15871588protected int getCollatedCopies() {1589return 1;1590}15911592private String[] printExecCmd(String printer, String options,1593boolean noJobSheet,1594String jobTitle, int copies, String spoolFile) {1595int PRINTER = 0x1;1596int OPTIONS = 0x2;1597int JOBTITLE = 0x4;1598int COPIES = 0x8;1599int NOSHEET = 0x10;1600int pFlags = 0;1601String[] execCmd;1602int ncomps = 2; // minimum number of print args1603int n = 0;16041605if (printer != null && !printer.isEmpty() && !printer.equals("lp")) {1606pFlags |= PRINTER;1607ncomps+=1;1608}1609if (options != null && !options.isEmpty()) {1610pFlags |= OPTIONS;1611ncomps+=1;1612}1613if (jobTitle != null && !jobTitle.isEmpty()) {1614pFlags |= JOBTITLE;1615ncomps+=1;1616}1617if (copies > 1) {1618pFlags |= COPIES;1619ncomps+=1;1620}1621if (noJobSheet) {1622pFlags |= NOSHEET;1623ncomps+=1;1624} else if (getPrintService().1625isAttributeCategorySupported(JobSheets.class)) {1626ncomps+=1; // for jobsheet1627}16281629String osname = System.getProperty("os.name");1630if (osname.equals("Linux") || osname.contains("OS X")) {1631execCmd = new String[ncomps];1632execCmd[n++] = "/usr/bin/lpr";1633if ((pFlags & PRINTER) != 0) {1634execCmd[n++] = "-P" + printer;1635}1636if ((pFlags & JOBTITLE) != 0) {1637execCmd[n++] = "-J" + jobTitle;1638}1639if ((pFlags & COPIES) != 0) {1640execCmd[n++] = "-#" + copies;1641}1642if ((pFlags & NOSHEET) != 0) {1643execCmd[n++] = "-h";1644} else if (getPrintService().1645isAttributeCategorySupported(JobSheets.class)) {1646execCmd[n++] = "-o job-sheets=standard";1647}1648if ((pFlags & OPTIONS) != 0) {1649execCmd[n++] = "-o" + options;1650}1651} else {1652ncomps+=1; //add 1 arg for lp1653execCmd = new String[ncomps];1654execCmd[n++] = "/usr/bin/lp";1655execCmd[n++] = "-c"; // make a copy of the spool file1656if ((pFlags & PRINTER) != 0) {1657execCmd[n++] = "-d" + printer;1658}1659if ((pFlags & JOBTITLE) != 0) {1660execCmd[n++] = "-t" + jobTitle;1661}1662if ((pFlags & COPIES) != 0) {1663execCmd[n++] = "-n" + copies;1664}1665if ((pFlags & NOSHEET) != 0) {1666execCmd[n++] = "-o nobanner";1667} else if (getPrintService().1668isAttributeCategorySupported(JobSheets.class)) {1669execCmd[n++] = "-o job-sheets=standard";1670}1671if ((pFlags & OPTIONS) != 0) {1672execCmd[n++] = "-o" + options;1673}1674}1675execCmd[n++] = spoolFile;1676return execCmd;1677}16781679private static int swapBGRtoRGB(byte[] image, int index, byte[] dest) {1680int destIndex = 0;1681while(index < image.length-2 && destIndex < dest.length-2) {1682dest[destIndex++] = image[index+2];1683dest[destIndex++] = image[index+1];1684dest[destIndex++] = image[index+0];1685index+=3;1686}1687return index;1688}16891690/*1691* Currently CharToByteConverter.getCharacterEncoding() return values are1692* not fixed yet. These are used as the part of the key of1693* psfont.properties. When those name are fixed this routine can1694* be erased.1695*/1696private String makeCharsetName(String name, char[] chs) {1697if (name.equals("Cp1252") || name.equals("ISO8859_1")) {1698return "latin1";1699} else if (name.equals("UTF8")) {1700// same as latin 1 if all chars < 2561701for (int i=0; i < chs.length; i++) {1702if (chs[i] > 255) {1703return name.toLowerCase();1704}1705}1706return "latin1";1707} else if (name.startsWith("ISO8859")) {1708// same as latin 1 if all chars < 1281709for (int i=0; i < chs.length; i++) {1710if (chs[i] > 127) {1711return name.toLowerCase();1712}1713}1714return "latin1";1715} else {1716return name.toLowerCase();1717}1718}17191720private void prepDrawing() {17211722/* Pop gstates until we can set the needed clip1723* and transform or until we are at the outer most1724* gstate.1725*/1726while (isOuterGState() == false1727&& (getGState().canSetClip(mLastClip) == false1728|| getGState().mTransform.equals(mLastTransform) == false)) {172917301731grestore();1732}17331734/* Set the color. This can push the color to the1735* outer most gsave which is often a good thing.1736*/1737getGState().emitPSColor(mLastColor);17381739/* We do not want to change the outermost1740* transform or clip so if we are at the1741* outer clip the generate a gsave.1742*/1743if (isOuterGState()) {1744gsave();1745getGState().emitTransform(mLastTransform);1746getGState().emitPSClip(mLastClip);1747}17481749/* Set the font if we have been asked to. It is1750* important that the font is set after the1751* transform in order to get the font size1752* correct.1753*/1754// if (g != null) {1755// getGState().emitPSFont(g, mLastFont);1756// }17571758}17591760/**1761* Return the GState that is currently on top1762* of the GState stack. There should always be1763* a GState on top of the stack. If there isn't1764* then this method will throw an IndexOutOfBounds1765* exception.1766*/1767private GState getGState() {1768int count = mGStateStack.size();1769return mGStateStack.get(count - 1);1770}17711772/**1773* Emit a PostScript gsave command and add a1774* new GState on to our stack which represents1775* the printer's gstate stack.1776*/1777private void gsave() {1778GState oldGState = getGState();1779mGStateStack.add(new GState(oldGState));1780mPSStream.println(GSAVE_STR);1781}17821783/**1784* Emit a PostScript grestore command and remove1785* a GState from our stack which represents the1786* printer's gstate stack.1787*/1788private void grestore() {1789int count = mGStateStack.size();1790mGStateStack.remove(count - 1);1791mPSStream.println(GRESTORE_STR);1792}17931794/**1795* Return true if the current GState is the1796* outermost GState and therefore should not1797* be restored.1798*/1799private boolean isOuterGState() {1800return mGStateStack.size() == 1;1801}18021803/**1804* A stack of GStates is maintained to model the printer's1805* gstate stack. Each GState holds information about1806* the current graphics attributes.1807*/1808private class GState{1809Color mColor;1810Shape mClip;1811Font mFont;1812AffineTransform mTransform;18131814GState() {1815mColor = Color.black;1816mClip = null;1817mFont = null;1818mTransform = new AffineTransform();1819}18201821GState(GState copyGState) {1822mColor = copyGState.mColor;1823mClip = copyGState.mClip;1824mFont = copyGState.mFont;1825mTransform = copyGState.mTransform;1826}18271828boolean canSetClip(Shape clip) {18291830return mClip == null || mClip.equals(clip);1831}183218331834void emitPSClip(Shape clip) {1835if (clip != null1836&& (mClip == null || mClip.equals(clip) == false)) {1837String saveFillOp = mFillOpStr;1838String saveClipOp = mClipOpStr;1839convertToPSPath(clip.getPathIterator(new AffineTransform()));1840selectClipPath();1841mClip = clip;1842/* The clip is a shape and has reset the winding rule state */1843mClipOpStr = saveFillOp;1844mFillOpStr = saveFillOp;1845}1846}18471848void emitTransform(AffineTransform transform) {18491850if (transform != null && transform.equals(mTransform) == false) {1851double[] matrix = new double[6];1852transform.getMatrix(matrix);1853mPSStream.println("[" + (float)matrix[0]1854+ " " + (float)matrix[1]1855+ " " + (float)matrix[2]1856+ " " + (float)matrix[3]1857+ " " + (float)matrix[4]1858+ " " + (float)matrix[5]1859+ "] concat");18601861mTransform = transform;1862}1863}18641865void emitPSColor(Color color) {1866if (color != null && color.equals(mColor) == false) {1867float[] rgb = color.getRGBColorComponents(null);18681869/* If the color is a gray value then use1870* setgray.1871*/1872if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) {1873mPSStream.println(rgb[0] + SETGRAY_STR);18741875/* It's not gray so use setrgbcolor.1876*/1877} else {1878mPSStream.println(rgb[0] + " "1879+ rgb[1] + " "1880+ rgb[2] + " "1881+ SETRGBCOLOR_STR);1882}18831884mColor = color;18851886}1887}18881889void emitPSFont(int psFontIndex, float fontSize) {1890mPSStream.println(fontSize + " " +1891psFontIndex + " " + SetFontName);1892}1893}18941895/**1896* Given a Java2D {@code PathIterator} instance,1897* this method translates that into a PostScript path..1898*/1899void convertToPSPath(PathIterator pathIter) {19001901float[] segment = new float[6];1902int segmentType;19031904/* Map the PathIterator's fill rule into the PostScript1905* fill rule.1906*/1907int fillRule;1908if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) {1909fillRule = FILL_EVEN_ODD;1910} else {1911fillRule = FILL_WINDING;1912}19131914beginPath();19151916setFillMode(fillRule);19171918while (pathIter.isDone() == false) {1919segmentType = pathIter.currentSegment(segment);19201921switch (segmentType) {1922case PathIterator.SEG_MOVETO:1923moveTo(segment[0], segment[1]);1924break;19251926case PathIterator.SEG_LINETO:1927lineTo(segment[0], segment[1]);1928break;19291930/* Convert the quad path to a bezier.1931*/1932case PathIterator.SEG_QUADTO:1933float lastX = getPenX();1934float lastY = getPenY();1935float c1x = lastX + (segment[0] - lastX) * 2 / 3;1936float c1y = lastY + (segment[1] - lastY) * 2 / 3;1937float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3;1938float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3;1939bezierTo(c1x, c1y,1940c2x, c2y,1941segment[2], segment[3]);1942break;19431944case PathIterator.SEG_CUBICTO:1945bezierTo(segment[0], segment[1],1946segment[2], segment[3],1947segment[4], segment[5]);1948break;19491950case PathIterator.SEG_CLOSE:1951closeSubpath();1952break;1953}195419551956pathIter.next();1957}1958}19591960/*1961* Fill the path defined by {@code pathIter}1962* with the specified color.1963* The path is provided in current user space.1964*/1965protected void deviceFill(PathIterator pathIter, Color color,1966AffineTransform tx, Shape clip) {19671968if (Double.isNaN(tx.getScaleX()) ||1969Double.isNaN(tx.getScaleY()) ||1970Double.isNaN(tx.getShearX()) ||1971Double.isNaN(tx.getShearY()) ||1972Double.isNaN(tx.getTranslateX()) ||1973Double.isNaN(tx.getTranslateY())) {1974return;1975}1976setTransform(tx);1977setClip(clip);1978setColor(color);1979convertToPSPath(pathIter);1980/* Specify the path to fill as the clip, this ensures that only1981* pixels which are inside the path will be filled, which is1982* what the Java 2D APIs specify1983*/1984mPSStream.println(GSAVE_STR);1985selectClipPath();1986fillPath();1987mPSStream.println(GRESTORE_STR + " " + NEWPATH_STR);1988}19891990/*1991* Run length encode byte array in a form suitable for decoding1992* by the PS Level 2 filter RunLengthDecode.1993* Array data to encode is inArr. Encoded data is written to outArr1994* outArr must be long enough to hold the encoded data but this1995* can't be known ahead of time.1996* A safe assumption is to use double the length of the input array.1997* This is then copied into a new array of the correct length which1998* is returned.1999* Algorithm:2000* Encoding is a lead byte followed by data bytes.2001* Lead byte of 0->127 indicates leadByte + 1 distinct bytes follow2002* Lead byte of 129->255 indicates 257 - leadByte is the number of times2003* the following byte is repeated in the source.2004* 128 is a special lead byte indicating end of data (EOD) and is2005* written as the final byte of the returned encoded data.2006*/2007private byte[] rlEncode(byte[] inArr) {20082009int inIndex = 0;2010int outIndex = 0;2011int startIndex = 0;2012int runLen = 0;2013byte[] outArr = new byte[(inArr.length * 2) +2];2014while (inIndex < inArr.length) {2015if (runLen == 0) {2016startIndex = inIndex++;2017runLen=1;2018}20192020while (runLen < 128 && inIndex < inArr.length &&2021inArr[inIndex] == inArr[startIndex]) {2022runLen++; // count run of same value2023inIndex++;2024}20252026if (runLen > 1) {2027outArr[outIndex++] = (byte)(257 - runLen);2028outArr[outIndex++] = inArr[startIndex];2029runLen = 0;2030continue; // back to top of while loop.2031}20322033// if reach here have a run of different values, or at the end.2034while (runLen < 128 && inIndex < inArr.length &&2035inArr[inIndex] != inArr[inIndex-1]) {2036runLen++; // count run of different values2037inIndex++;2038}2039outArr[outIndex++] = (byte)(runLen - 1);2040for (int i = startIndex; i < startIndex+runLen; i++) {2041outArr[outIndex++] = inArr[i];2042}2043runLen = 0;2044}2045outArr[outIndex++] = (byte)128;2046byte[] encodedData = new byte[outIndex];2047System.arraycopy(outArr, 0, encodedData, 0, outIndex);20482049return encodedData;2050}20512052/* written acc. to Adobe Spec. "Filtered Files: ASCIIEncode Filter",2053* "PS Language Reference Manual, 2nd edition: Section 3.13"2054*/2055private byte[] ascii85Encode(byte[] inArr) {2056byte[] outArr = new byte[((inArr.length+4) * 5 / 4) + 2];2057long p1 = 85;2058long p2 = p1*p1;2059long p3 = p1*p2;2060long p4 = p1*p3;2061byte pling = '!';20622063int i = 0;2064int olen = 0;2065long val, rem;20662067while (i+3 < inArr.length) {2068val = ((long)((inArr[i++]&0xff))<<24) +2069((long)((inArr[i++]&0xff))<<16) +2070((long)((inArr[i++]&0xff))<< 8) +2071((long)(inArr[i++]&0xff));2072if (val == 0) {2073outArr[olen++] = 'z';2074} else {2075rem = val;2076outArr[olen++] = (byte)(rem / p4 + pling); rem = rem % p4;2077outArr[olen++] = (byte)(rem / p3 + pling); rem = rem % p3;2078outArr[olen++] = (byte)(rem / p2 + pling); rem = rem % p2;2079outArr[olen++] = (byte)(rem / p1 + pling); rem = rem % p1;2080outArr[olen++] = (byte)(rem + pling);2081}2082}2083// input not a multiple of 4 bytes, write partial output.2084if (i < inArr.length) {2085int n = inArr.length - i; // n bytes remain to be written20862087val = 0;2088while (i < inArr.length) {2089val = (val << 8) + (inArr[i++]&0xff);2090}20912092int append = 4 - n;2093while (append-- > 0) {2094val = val << 8;2095}2096byte []c = new byte[5];2097rem = val;2098c[0] = (byte)(rem / p4 + pling); rem = rem % p4;2099c[1] = (byte)(rem / p3 + pling); rem = rem % p3;2100c[2] = (byte)(rem / p2 + pling); rem = rem % p2;2101c[3] = (byte)(rem / p1 + pling); rem = rem % p1;2102c[4] = (byte)(rem + pling);21032104for (int b = 0; b < n+1 ; b++) {2105outArr[olen++] = c[b];2106}2107}21082109// write EOD marker.2110outArr[olen++]='~'; outArr[olen++]='>';21112112/* The original intention was to insert a newline after every 78 bytes.2113* This was mainly intended for legibility but I decided against this2114* partially because of the (small) amount of extra space, and2115* partially because for line breaks either would have to hardwire2116* ascii 10 (newline) or calculate space in bytes to allocate for2117* the platform's newline byte sequence. Also need to be careful2118* about where its inserted:2119* Ascii 85 decoder ignores white space except for one special case:2120* you must ensure you do not split the EOD marker across lines.2121*/2122byte[] retArr = new byte[olen];2123System.arraycopy(outArr, 0, retArr, 0, olen);2124return retArr;21252126}21272128/**2129* PluginPrinter generates EPSF wrapped with a header and trailer2130* comment. This conforms to the new requirements of Mozilla 1.72131* and FireFox 1.5 and later. Earlier versions of these browsers2132* did not support plugin printing in the general sense (not just Java).2133* A notable limitation of these browsers is that they handle plugins2134* which would span page boundaries by scaling plugin content to fit on a2135* single page. This means white space is left at the bottom of the2136* previous page and its impossible to print these cases as they appear on2137* the web page. This is contrast to how the same browsers behave on2138* Windows where it renders as on-screen.2139* Cases where the content fits on a single page do work fine, and they2140* are the majority of cases.2141* The scaling that the browser specifies to make the plugin content fit2142* when it is larger than a single page can hold is non-uniform. It2143* scales the axis in which the content is too large just enough to2144* ensure it fits. For content which is extremely long this could lead2145* to noticeable distortion. However that is probably rare enough that2146* its not worth compensating for that here, but we can revisit that if2147* needed, and compensate by making the scale for the other axis the2148* same.2149*/2150public static class PluginPrinter implements Printable {21512152private EPSPrinter epsPrinter;2153private Component applet;2154private PrintStream stream;2155private String epsTitle;2156private int bx, by, bw, bh;2157private int width, height;21582159/**2160* This is called from the Java Plug-in to print an Applet's2161* contents as EPS to a postscript stream provided by the browser.2162* @param applet the applet component to print.2163* @param stream the print stream provided by the plug-in2164* @param x the x location of the applet panel in the browser window2165* @param y the y location of the applet panel in the browser window2166* @param w the width of the applet panel in the browser window2167* @param h the width of the applet panel in the browser window2168*/2169@SuppressWarnings("deprecation")2170public PluginPrinter(Component applet,2171PrintStream stream,2172int x, int y, int w, int h) {21732174this.applet = applet;2175this.epsTitle = "Java Plugin Applet";2176this.stream = stream;2177bx = x;2178by = y;2179bw = w;2180bh = h;2181width = applet.size().width;2182height = applet.size().height;2183epsPrinter = new EPSPrinter(this, epsTitle, stream,21840, 0, width, height);2185}21862187public void printPluginPSHeader() {2188stream.println("%%BeginDocument: JavaPluginApplet");2189}21902191public void printPluginApplet() {2192try {2193epsPrinter.print();2194} catch (PrinterException e) {2195}2196}21972198public void printPluginPSTrailer() {2199stream.println("%%EndDocument: JavaPluginApplet");2200stream.flush();2201}22022203public void printAll() {2204printPluginPSHeader();2205printPluginApplet();2206printPluginPSTrailer();2207}22082209public int print(Graphics g, PageFormat pf, int pgIndex) {2210if (pgIndex > 0) {2211return Printable.NO_SUCH_PAGE;2212} else {2213// "aware" client code can detect that its been passed a2214// PrinterGraphics and could theoretically print2215// differently. I think this is more likely useful than2216// a problem.2217applet.printAll(g);2218return Printable.PAGE_EXISTS;2219}2220}22212222}22232224/*2225* This class can take an application-client supplied printable object2226* and send the result to a stream.2227* The application does not need to send any postscript to this stream2228* unless it needs to specify a translation etc.2229* It assumes that its importing application obeys all the conventions2230* for importation of EPS. See Appendix H - Encapsulated Postscript File2231* Format - of the Adobe Postscript Language Reference Manual, 2nd edition.2232* This class could be used as the basis for exposing the ability to2233* generate EPSF from 2D graphics as a StreamPrintService.2234* In that case a MediaPrintableArea attribute could be used to2235* communicate the bounding box.2236*/2237public static class EPSPrinter implements Pageable {22382239private PageFormat pf;2240private PSPrinterJob job;2241private int llx, lly, urx, ury;2242private Printable printable;2243private PrintStream stream;2244private String epsTitle;22452246public EPSPrinter(Printable printable, String title,2247PrintStream stream,2248int x, int y, int wid, int hgt) {22492250this.printable = printable;2251this.epsTitle = title;2252this.stream = stream;2253llx = x;2254lly = y;2255urx = llx+wid;2256ury = lly+hgt;2257// construct a PageFormat with zero margins representing the2258// exact bounds of the applet. ie construct a theoretical2259// paper which happens to exactly match applet panel size.2260Paper p = new Paper();2261p.setSize((double)wid, (double)hgt);2262p.setImageableArea(0.0,0.0, (double)wid, (double)hgt);2263pf = new PageFormat();2264pf.setPaper(p);2265}22662267public void print() throws PrinterException {2268stream.println("%!PS-Adobe-3.0 EPSF-3.0");2269stream.println("%%BoundingBox: " +2270llx + " " + lly + " " + urx + " " + ury);2271stream.println("%%Title: " + epsTitle);2272stream.println("%%Creator: Java Printing");2273stream.println("%%CreationDate: " + new java.util.Date());2274stream.println("%%EndComments");2275stream.println("/pluginSave save def");2276stream.println("mark"); // for restoring stack state on return22772278job = new PSPrinterJob();2279job.epsPrinter = this; // modifies the behaviour of PSPrinterJob2280job.mPSStream = stream;2281job.mDestType = RasterPrinterJob.STREAM; // prevents closure22822283job.startDoc();2284try {2285job.printPage(this, 0);2286} catch (Throwable t) {2287if (t instanceof PrinterException) {2288throw (PrinterException)t;2289} else {2290throw new PrinterException(t.toString());2291}2292} finally {2293stream.println("cleartomark"); // restore stack state2294stream.println("pluginSave restore");2295job.endDoc();2296}2297stream.flush();2298}22992300public int getNumberOfPages() {2301return 1;2302}23032304public PageFormat getPageFormat(int pgIndex) {2305if (pgIndex > 0) {2306throw new IndexOutOfBoundsException("pgIndex");2307} else {2308return pf;2309}2310}23112312public Printable getPrintable(int pgIndex) {2313if (pgIndex > 0) {2314throw new IndexOutOfBoundsException("pgIndex");2315} else {2316return printable;2317}2318}23192320}2321}232223232324