Skip to content

Commit

Permalink
8311033: [macos] PrinterJob does not take into account Sides attribute
Browse files Browse the repository at this point in the history
Backport-of: a3d67231a71fbe37c509fcedd54c679b4644c0d9
  • Loading branch information
Alexander Scherbatiy committed Aug 22, 2023
1 parent 399633c commit d0f6931
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
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.*;
Expand Down Expand Up @@ -682,6 +683,24 @@ private Rectangle2D getPageFormatArea(PageFormat page) {
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.

Expand Down
42 changes: 42 additions & 0 deletions src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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];
Expand Down Expand Up @@ -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();
}
}
}

Expand All @@ -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];

Expand Down Expand Up @@ -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];
}
}
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,8 @@ public PrintService[] run() {
return false;
}

this.attributes = attributes;

if (!service.equals(newService)) {
try {
setPrintService(newService);
Expand Down
272 changes: 272 additions & 0 deletions test/jdk/javax/print/attribute/SidesAttributeTest.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
}

0 comments on commit d0f6931

Please sign in to comment.