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