8311033: [macos] PrinterJob does not take into account Sides attribute
Backport-of: a3d67231a71fbe37c509fcedd54c679b4644c0d9
This commit is contained in:
parent
4c123ac314
commit
47ed2e5b26
|
@ -45,6 +45,7 @@ import javax.print.attribute.standard.MediaPrintableArea;
|
|||
import javax.print.attribute.standard.MediaSize;
|
||||
import javax.print.attribute.standard.MediaSizeName;
|
||||
import javax.print.attribute.standard.PageRanges;
|
||||
import javax.print.attribute.standard.Sides;
|
||||
import javax.print.attribute.Attribute;
|
||||
|
||||
import sun.java2d.*;
|
||||
|
@ -684,6 +685,24 @@ public final class CPrinterJob extends RasterPrinterJob {
|
|||
return pageFormatArea;
|
||||
}
|
||||
|
||||
private int getSides() {
|
||||
return (this.sidesAttr == null) ? -1 : this.sidesAttr.getValue();
|
||||
}
|
||||
|
||||
private void setSides(int sides) {
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Sides[] sidesTable = new Sides[] {Sides.ONE_SIDED, Sides.TWO_SIDED_LONG_EDGE, Sides.TWO_SIDED_SHORT_EDGE};
|
||||
|
||||
if (sides >= 0 && sides < sidesTable.length) {
|
||||
Sides s = sidesTable[sides];
|
||||
attributes.add(s);
|
||||
this.sidesAttr = s;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean cancelCheck() {
|
||||
// This is called from the native side.
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@
|
|||
#import "GeomUtilities.h"
|
||||
#import "JNIUtilities.h"
|
||||
|
||||
#define ONE_SIDED 0
|
||||
#define TWO_SIDED_LONG_EDGE 1
|
||||
#define TWO_SIDED_SHORT_EDGE 2
|
||||
|
||||
static jclass sjc_Paper = NULL;
|
||||
static jclass sjc_PageFormat = NULL;
|
||||
static jclass sjc_CPrinterJob = NULL;
|
||||
|
@ -351,6 +355,24 @@ static void javaPageFormatToNSPrintInfo(JNIEnv* env, jobject srcPrintJob, jobjec
|
|||
[dstPrintInfo setPrinter:printer];
|
||||
}
|
||||
|
||||
static jint duplexModeToSides(PMDuplexMode duplexMode) {
|
||||
switch(duplexMode) {
|
||||
case kPMDuplexNone: return ONE_SIDED;
|
||||
case kPMDuplexTumble: return TWO_SIDED_SHORT_EDGE;
|
||||
case kPMDuplexNoTumble: return TWO_SIDED_LONG_EDGE;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static PMDuplexMode sidesToDuplexMode(jint sides) {
|
||||
switch(sides) {
|
||||
case ONE_SIDED: return kPMDuplexNone;
|
||||
case TWO_SIDED_SHORT_EDGE: return kPMDuplexTumble;
|
||||
case TWO_SIDED_LONG_EDGE: return kPMDuplexNoTumble;
|
||||
default: return kPMDuplexNone;
|
||||
}
|
||||
}
|
||||
|
||||
static void nsPrintInfoToJavaPrinterJob(JNIEnv* env, NSPrintInfo* src, jobject dstPrinterJob, jobject dstPageable)
|
||||
{
|
||||
GET_CPRINTERJOB_CLASS();
|
||||
|
@ -360,6 +382,7 @@ static void nsPrintInfoToJavaPrinterJob(JNIEnv* env, NSPrintInfo* src, jobject d
|
|||
DECLARE_METHOD(jm_setPageRangeAttribute, sjc_CPrinterJob, "setPageRangeAttribute", "(IIZ)V");
|
||||
DECLARE_METHOD(jm_setPrintToFile, sjc_CPrinterJob, "setPrintToFile", "(Z)V");
|
||||
DECLARE_METHOD(jm_setDestinationFile, sjc_CPrinterJob, "setDestinationFile", "(Ljava/lang/String;)V");
|
||||
DECLARE_METHOD(jm_setSides, sjc_CPrinterJob, "setSides", "(I)V");
|
||||
|
||||
// get the selected printer's name, and set the appropriate PrintService on the Java side
|
||||
NSString *name = [[src printer] name];
|
||||
|
@ -420,6 +443,12 @@ static void nsPrintInfoToJavaPrinterJob(JNIEnv* env, NSPrintInfo* src, jobject d
|
|||
jFirstPage, jLastPage, isRangeSet); // AWT_THREADING Safe (known object)
|
||||
CHECK_EXCEPTION();
|
||||
|
||||
PMDuplexMode duplexSetting;
|
||||
if (PMGetDuplex(src.PMPrintSettings, &duplexSetting) == noErr) {
|
||||
jint sides = duplexModeToSides(duplexSetting);
|
||||
(*env)->CallVoidMethod(env, dstPrinterJob, jm_setSides, sides); // AWT_THREADING Safe (known object)
|
||||
CHECK_EXCEPTION();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,6 +467,8 @@ static void javaPrinterJobToNSPrintInfo(JNIEnv* env, jobject srcPrinterJob, jobj
|
|||
DECLARE_METHOD(jm_getNumberOfPages, jc_Pageable, "getNumberOfPages", "()I");
|
||||
DECLARE_METHOD(jm_getPageFormat, sjc_CPrinterJob, "getPageFormatFromAttributes", "()Ljava/awt/print/PageFormat;");
|
||||
DECLARE_METHOD(jm_getDestinationFile, sjc_CPrinterJob, "getDestinationFile", "()Ljava/lang/String;");
|
||||
DECLARE_METHOD(jm_getSides, sjc_CPrinterJob, "getSides", "()I");
|
||||
|
||||
|
||||
NSMutableDictionary* printingDictionary = [dst dictionary];
|
||||
|
||||
|
@ -496,6 +527,17 @@ static void javaPrinterJobToNSPrintInfo(JNIEnv* env, jobject srcPrinterJob, jobj
|
|||
} else {
|
||||
[dst setJobDisposition:NSPrintSpoolJob];
|
||||
}
|
||||
|
||||
jint sides = (*env)->CallIntMethod(env, srcPrinterJob, jm_getSides);
|
||||
CHECK_EXCEPTION();
|
||||
|
||||
if (sides >= 0) {
|
||||
PMDuplexMode duplexMode = sidesToDuplexMode(sides);
|
||||
PMPrintSettings printSettings = dst.PMPrintSettings;
|
||||
if (PMSetDuplex(printSettings, duplexMode) == noErr) {
|
||||
[dst updateFromPMPrintSettings];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1082,6 +1082,8 @@ public abstract class RasterPrinterJob extends PrinterJob {
|
|||
return false;
|
||||
}
|
||||
|
||||
this.attributes = attributes;
|
||||
|
||||
if (!service.equals(newService)) {
|
||||
try {
|
||||
setPrintService(newService);
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, BELLSOFT. 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug JDK-8311033
|
||||
* @summary [macos] PrinterJob does not take into account Sides attribute
|
||||
* @run main/manual SidesAttributeTest
|
||||
*/
|
||||
|
||||
import javax.print.PrintService;
|
||||
import javax.print.attribute.Attribute;
|
||||
import javax.print.attribute.HashPrintRequestAttributeSet;
|
||||
import javax.print.attribute.PrintRequestAttributeSet;
|
||||
import javax.print.attribute.standard.Sides;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.print.PageFormat;
|
||||
import java.awt.print.Printable;
|
||||
import java.awt.print.PrinterException;
|
||||
import java.awt.print.PrinterJob;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SidesAttributeTest {
|
||||
|
||||
private static final long TIMEOUT = 10 * 60_000;
|
||||
private static volatile boolean testPassed = true;
|
||||
private static volatile boolean testFinished = false;
|
||||
private static volatile boolean timeout = false;
|
||||
|
||||
private static volatile int testCount;
|
||||
private static volatile int testTotalCount;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
|
||||
Set<Attribute> supportedSides = getSupportedSidesAttributes();
|
||||
if (supportedSides.size() > 1) {
|
||||
testTotalCount = supportedSides.size();
|
||||
testPrint(Sides.ONE_SIDED, supportedSides);
|
||||
testPrint(Sides.DUPLEX, supportedSides);
|
||||
testPrint(Sides.TUMBLE, supportedSides);
|
||||
}
|
||||
testFinished = true;
|
||||
});
|
||||
|
||||
long time = System.currentTimeMillis() + TIMEOUT;
|
||||
|
||||
while (System.currentTimeMillis() < time) {
|
||||
if (!testPassed || testFinished) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
timeout = true;
|
||||
|
||||
closeDialogs();
|
||||
|
||||
if (!testPassed) {
|
||||
throw new Exception("Test failed!");
|
||||
}
|
||||
|
||||
if (testCount != testTotalCount) {
|
||||
throw new Exception(
|
||||
"Timeout: " + testCount + " tests passed out from " + testTotalCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static void print(Sides sides) throws PrinterException {
|
||||
PrintRequestAttributeSet attr = new HashPrintRequestAttributeSet();
|
||||
attr.add(sides);
|
||||
|
||||
for (Attribute attribute : attr.toArray()) {
|
||||
System.out.printf("Used print request attribute: %s%n", attribute);
|
||||
}
|
||||
|
||||
PrinterJob job = PrinterJob.getPrinterJob();
|
||||
job.setPrintable(new SidesAttributePrintable(sides));
|
||||
|
||||
job.print(attr);
|
||||
}
|
||||
|
||||
private static class SidesAttributePrintable implements Printable {
|
||||
|
||||
private final Sides sidesAttr;
|
||||
|
||||
public SidesAttributePrintable(Sides sidesAttr) {
|
||||
this.sidesAttr = sidesAttr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
|
||||
|
||||
if (pageIndex >= 2) {
|
||||
return NO_SUCH_PAGE;
|
||||
}
|
||||
|
||||
int x = (int) (pageFormat.getImageableX() + pageFormat.getImageableWidth() / 10);
|
||||
int y = (int) (pageFormat.getImageableY() + pageFormat.getImageableHeight() / 5);
|
||||
|
||||
Graphics2D g = (Graphics2D) graphics;
|
||||
String text = getPageText(sidesAttr, pageIndex + 1);
|
||||
g.drawString(text, x, y);
|
||||
return PAGE_EXISTS;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPageText(Sides sides, int page) {
|
||||
return String.format("Page: %d - %s", page, getSidesText(sides));
|
||||
}
|
||||
|
||||
private static String getSidesText(Sides sides) {
|
||||
if (Sides.ONE_SIDED.equals(sides)) {
|
||||
return "ONE_SIDED";
|
||||
} else if (Sides.TWO_SIDED_SHORT_EDGE.equals(sides)) {
|
||||
return "TWO_SIDED_SHORT_EDGE (TUMBLE)";
|
||||
} else if (Sides.TWO_SIDED_LONG_EDGE.equals(sides)) {
|
||||
return "TWO_SIDED_LONG_EDGE (DUPLEX)";
|
||||
}
|
||||
throw new RuntimeException("Unknown sides attribute: " + sides);
|
||||
}
|
||||
|
||||
private static String getSidesDescription(Sides sides) {
|
||||
if (Sides.ONE_SIDED.equals(sides)) {
|
||||
return "a one-sided document";
|
||||
} else if (Sides.TWO_SIDED_SHORT_EDGE.equals(sides)) {
|
||||
return "double-sided document along the short edge of the paper";
|
||||
} else if (Sides.TWO_SIDED_LONG_EDGE.equals(sides)) {
|
||||
return "double-sided document along the long edge of the paper";
|
||||
}
|
||||
throw new RuntimeException("Unknown sides attribute: " + sides);
|
||||
}
|
||||
|
||||
private static Set<Attribute> getSupportedSidesAttributes() {
|
||||
Set<Attribute> supportedSides = new HashSet<>();
|
||||
|
||||
PrinterJob printerJob = PrinterJob.getPrinterJob();
|
||||
PrintService service = printerJob.getPrintService();
|
||||
|
||||
Object obj = service.getSupportedAttributeValues(Sides.class, null, null);
|
||||
if (obj instanceof Attribute[]) {
|
||||
Attribute[] attr = (Attribute[]) obj;
|
||||
Collections.addAll(supportedSides, attr);
|
||||
}
|
||||
|
||||
return supportedSides;
|
||||
}
|
||||
|
||||
private static void pass() {
|
||||
testCount++;
|
||||
}
|
||||
|
||||
private static void fail(Sides sides) {
|
||||
System.out.printf("Failed test: %s%n", getSidesText(sides));
|
||||
testPassed = false;
|
||||
}
|
||||
|
||||
private static void runPrint(Sides sides) {
|
||||
try {
|
||||
print(sides);
|
||||
} catch (PrinterException e) {
|
||||
fail(sides);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void testPrint(Sides sides, Set<Attribute> supportedSides) {
|
||||
|
||||
if (!supportedSides.contains(sides) || !testPassed || timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] instructions = {
|
||||
"Up to " + testTotalCount + " tests will run and it will test all the cases",
|
||||
"supported by the printer.",
|
||||
"",
|
||||
"The test is " + (testCount + 1) + " from " + testTotalCount + ".",
|
||||
"",
|
||||
"On-screen inspection is not possible for this printing-specific",
|
||||
"test therefore its only output is two printed pages (one or two sided).",
|
||||
"To be able to run this test it is required to have a default",
|
||||
"printer configured in your user environment.",
|
||||
"",
|
||||
"Visual inspection of the printed pages is needed.",
|
||||
"A passing test will print 2 pages:",
|
||||
" - the first page with the text: " + getPageText(sides, 1),
|
||||
" - the second page with the text: " + getPageText(sides, 2),
|
||||
"",
|
||||
"The test fails if the pages are not printed according to the tested",
|
||||
getSidesText(sides) + " attribute where " + getSidesDescription(sides),
|
||||
"needs to be printed.",
|
||||
"",
|
||||
};
|
||||
|
||||
String title = String.format("Print %s sides test: %d from %d",
|
||||
getSidesText(sides), testCount + 1, testTotalCount);
|
||||
final JDialog dialog = new JDialog((Frame) null, title, Dialog.ModalityType.DOCUMENT_MODAL);
|
||||
JTextArea textArea = new JTextArea(String.join("\n", instructions));
|
||||
textArea.setEditable(false);
|
||||
final JButton testButton = new JButton("Start Test");
|
||||
final JButton passButton = new JButton("PASS");
|
||||
passButton.setEnabled(false);
|
||||
passButton.addActionListener((e) -> {
|
||||
pass();
|
||||
dialog.dispose();
|
||||
});
|
||||
final JButton failButton = new JButton("FAIL");
|
||||
failButton.setEnabled(false);
|
||||
failButton.addActionListener((e) -> {
|
||||
fail(sides);
|
||||
dialog.dispose();
|
||||
});
|
||||
testButton.addActionListener((e) -> {
|
||||
testButton.setEnabled(false);
|
||||
runPrint(sides);
|
||||
passButton.setEnabled(true);
|
||||
failButton.setEnabled(true);
|
||||
});
|
||||
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
mainPanel.add(textArea, BorderLayout.CENTER);
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout());
|
||||
buttonPanel.add(testButton);
|
||||
buttonPanel.add(passButton);
|
||||
buttonPanel.add(failButton);
|
||||
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||
dialog.add(mainPanel);
|
||||
dialog.pack();
|
||||
dialog.setVisible(true);
|
||||
dialog.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
System.out.println("Dialog closing");
|
||||
fail(sides);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void closeDialogs() {
|
||||
for (Window w : Dialog.getWindows()) {
|
||||
w.dispose();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue