8251928: [macos] the printer DPI always be 72, cause some content lost when print out

Reviewed-by: psadhukhan, prr
This commit is contained in:
GennadiyKrivoshein 2025-11-11 03:49:39 +00:00 committed by Prasanta Sadhukhan
parent e1c952608d
commit 76a1109d6f
5 changed files with 378 additions and 12 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2025, 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
@ -32,6 +32,7 @@ import java.awt.GraphicsDevice;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.VolatileImage;
@ -45,10 +46,16 @@ public final class CPrinterGraphicsConfig extends GraphicsConfiguration {
private final GraphicsDevice device;
private final PageFormat pf;
private final AffineTransform deviceTransform;
public CPrinterGraphicsConfig(PageFormat pf) {
this(pf, null);
}
CPrinterGraphicsConfig(PageFormat pf, AffineTransform deviceTransform) {
this.device = new CPrinterDevice(this);
this.pf = pf;
this.deviceTransform = deviceTransform;
}
public PageFormat getPageFormat() {
@ -178,7 +185,8 @@ public final class CPrinterGraphicsConfig extends GraphicsConfiguration {
*/
@Override
public AffineTransform getDefaultTransform() {
return new AffineTransform();
return deviceTransform == null ?
new AffineTransform() : new AffineTransform(deviceTransform);
}
/**
@ -224,6 +232,10 @@ public final class CPrinterGraphicsConfig extends GraphicsConfiguration {
*/
@Override
public Rectangle getBounds() {
return new Rectangle(0, 0, (int)pf.getWidth(), (int)pf.getHeight());
Point2D.Double pt = new Point2D.Double(pf.getWidth(), pf.getHeight());
Point2D.Double transformedPt = new Point2D.Double();
getDefaultTransform().transform(pt, transformedPt);
return new Rectangle(0, 0,
(int)transformedPt.getX(), (int)transformedPt.getY());
}
}

View File

@ -35,6 +35,7 @@ import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.SecondaryLoop;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.print.Pageable;
@ -85,6 +86,9 @@ public final class CPrinterJob extends RasterPrinterJob {
// future compatibility and the state keeping that it handles.
private static String sShouldNotReachHere = "Should not reach here.";
private static final double USER_SPACE_DPI = 72.0;
private static final int DEFAULT_DOC_DPI_X = 300;
private static final int DEFAULT_DOC_DPI_Y = 300;
private volatile SecondaryLoop printingLoop;
private AtomicReference<Throwable> printErrorRef = new AtomicReference<>();
@ -110,6 +114,9 @@ public final class CPrinterJob extends RasterPrinterJob {
private final Object fNSPrintInfoLock = new Object();
private final Object disposerReferent = new Object();
private double hRes = DEFAULT_DOC_DPI_X;
private double vRes = DEFAULT_DOC_DPI_Y;
static {
// AWT has to be initialized for the native code to function correctly.
Toolkit.getDefaultToolkit();
@ -461,8 +468,7 @@ public final class CPrinterJob extends RasterPrinterJob {
*/
@Override
protected double getXRes() {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
return hRes;
}
/**
@ -471,8 +477,31 @@ public final class CPrinterJob extends RasterPrinterJob {
*/
@Override
protected double getYRes() {
// NOTE: This is not used in the CPrinterJob code path.
return 0;
return vRes;
}
@Override
protected void setXYRes(double x, double y) {
hRes = x;
vRes = y;
}
/**
* Returns the resolution in dots per inch across the width
* of the page. This method take into account the page orientation.
*/
private double getXRes(PageFormat pageFormat) {
return pageFormat.getOrientation() == PageFormat.PORTRAIT ?
getXRes() : getYRes();
}
/**
* Returns the resolution in dots per inch across the height
* of the page. This method take into account the page orientation.
*/
private double getYRes(PageFormat pageFormat) {
return pageFormat.getOrientation() == PageFormat.PORTRAIT ?
getYRes() : getXRes();
}
/**
@ -817,7 +846,11 @@ public final class CPrinterJob extends RasterPrinterJob {
// This is called from the native side.
Runnable r = new Runnable() { public void run() {
try {
SurfaceData sd = CPrinterSurfaceData.createData(page, context); // Just stores page into an ivar
AffineTransform deviceTransform = new AffineTransform(
getXRes(page) / USER_SPACE_DPI, 0, 0,
getYRes(page) / USER_SPACE_DPI, 0, 0);
SurfaceData sd = CPrinterSurfaceData
.createData(page, deviceTransform, context); // Just stores page into an ivar
if (defaultFont == null) {
defaultFont = new Font("Dialog", Font.PLAIN, 12);
}
@ -873,6 +906,9 @@ public final class CPrinterJob extends RasterPrinterJob {
Rectangle2D pageFormatArea =
getPageFormatArea(pageFormat);
initPrinterGraphics(peekGraphics, pageFormatArea);
double scaleX = getXRes(pageFormat) / USER_SPACE_DPI;
double scaleY = getYRes(pageFormat) / USER_SPACE_DPI;
peekGraphics.scale(scaleX, scaleY);
// Do the assignment here!
ret[0] = pageFormat;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2025, 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
@ -26,6 +26,7 @@
package sun.lwawt.macosx;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.awt.print.PageFormat;
import java.nio.ByteBuffer;
@ -40,8 +41,8 @@ public final class CPrinterSurfaceData extends OSXSurfaceData{
// public static final SurfaceType IntArgbPQ = SurfaceType.IntArgb.deriveSubType(DESC_INT_ARGB_PQ);
public static final SurfaceType IntRgbPQ = SurfaceType.IntRgb.deriveSubType(DESC_INT_RGB_PQ);
static SurfaceData createData(PageFormat pf, long context) {
return new CPrinterSurfaceData(CPrinterGraphicsConfig.getConfig(pf), context);
static SurfaceData createData(PageFormat pf, AffineTransform deviceTransform, long context) {
return new CPrinterSurfaceData(new CPrinterGraphicsConfig(pf, deviceTransform), context);
}
private CPrinterSurfaceData(GraphicsConfiguration gc, long context) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2025, 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
@ -86,6 +86,8 @@ static jclass sjc_PAbortEx = NULL;
GET_CPRINTERJOB_CLASS();
DECLARE_METHOD(jm_printToPathGraphics, sjc_CPrinterJob, "printToPathGraphics",
"(Lsun/print/PeekGraphics;Ljava/awt/print/PrinterJob;Ljava/awt/print/Printable;Ljava/awt/print/PageFormat;IJ)V");
DECLARE_METHOD(jm_getXRes, sjc_CPrinterJob, "getXRes", "(Ljava/awt/print/PageFormat;)D");
DECLARE_METHOD(jm_getYRes, sjc_CPrinterJob, "getYRes", "(Ljava/awt/print/PageFormat;)D");
// Create and draw into a new CPrinterGraphics with the current Context.
assert(fCurPageFormat != NULL);
@ -103,6 +105,21 @@ static jclass sjc_PAbortEx = NULL;
jlong context = ptr_to_jlong([printLoop context]);
CGContextRef cgRef = (CGContextRef)[[printLoop context] graphicsPort];
// Scale from the java document DPI to the user space DPI
jdouble hRes = (*env)->CallDoubleMethod(env, fPrinterJob, jm_getXRes, fCurPageFormat);
CHECK_EXCEPTION();
jdouble vRes = (*env)->CallDoubleMethod(env, fPrinterJob, jm_getYRes, fCurPageFormat);
CHECK_EXCEPTION();
if (hRes > 0 && vRes > 0) {
double defaultDeviceDPI = 72;
double scaleX = defaultDeviceDPI / hRes;
double scaleY = defaultDeviceDPI / vRes;
if (scaleX != 1 || scaleY != 1) {
CGContextScaleCTM(cgRef, scaleX, scaleY);
}
}
CGContextSaveGState(cgRef); //04/28/2004: state needs to be saved here due to addition of lazy state management
(*env)->CallVoidMethod(env, fPrinterJob, jm_printToPathGraphics, fCurPeekGraphics, fPrinterJob,

View File

@ -0,0 +1,300 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 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.
*/
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.ResolutionSyntax;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.PrinterResolution;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.border.EmptyBorder;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.GeneralPath;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
/*
* @test
* @bug 8251928
* @key printer
* @summary Printable.print method should reflect printer's DPI
* @library /java/awt/regtesthelpers
* @requires os.family == "mac"
* @build PassFailJFrame
* @run main/manual PrintablePrintDPI
*/
public class PrintablePrintDPI implements Printable {
private static final double PAPER_DPI = 72.0;
private static final int DEFAULT_DOCUMENT_DPI = 300;
private static final int UNITS = ResolutionSyntax.DPI;
private static final String INSTRUCTIONS = """
This test checks document DPI.
To be able to run this test it is required to have a
printer configured in your user environment.
Test's steps:
- Choose a printer.
- Choose a printer resolution.
- Press 'Print' button.
Visual inspection of the printed pages is needed.
A passing test will print chosen DPI on the printed page,
2 vertical and 2 horizontal lines.
""";
private final PrintService printService;
private final PrinterResolution printerResolution;
public static void main(String[] args) throws Exception {
PrintService[] availablePrintServices = PrintServiceLookup.lookupPrintServices(null, null);
if (availablePrintServices.length == 0) {
System.out.println("Available print services not found");
return;
}
PassFailJFrame.builder()
.instructions(INSTRUCTIONS)
.testTimeOut(300)
.title("Document DPI test")
.testUI(createTestWindow(availablePrintServices))
.build()
.awaitAndCheck();
}
private static Window createTestWindow(final PrintService[] availablePrintServices) {
final Window frame = new JFrame("Choose service to test");
JPanel pnlMain = new JPanel();
pnlMain.setBorder(new EmptyBorder(5, 5, 5, 5));
pnlMain.setLayout(new GridLayout(3, 1, 5, 5));
JLabel lblServices = new JLabel("Available services");
JLabel lblResolutions = new JLabel("Available resolutions");
JButton btnPrint = new JButton("Print");
btnPrint.setEnabled(false);
JComboBox<PrinterResolution> cbResolutions = new JComboBox<>();
cbResolutions.setRenderer(new ListCellRenderer<PrinterResolution>() {
@Override
public Component getListCellRendererComponent(JList<? extends PrinterResolution> list,
PrinterResolution value,
int index, boolean isSelected, boolean cellHasFocus) {
String str = value == null ? "" :
String.format("%dx%d DPI",
value.getCrossFeedResolution(UNITS), value.getFeedResolution(UNITS));
return new JLabel(str);
}
});
cbResolutions.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
btnPrint.setEnabled(cbResolutions.getSelectedItem() != null);
}
});
JComboBox<PrintService> cbServices = new JComboBox<>();
cbServices.setRenderer(new ListCellRenderer<PrintService>() {
@Override
public Component getListCellRendererComponent(JList<? extends PrintService> list, PrintService value,
int index, boolean isSelected, boolean cellHasFocus) {
return new JLabel(value == null ? "" : value.getName());
}
});
cbServices.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PrintService ps = (PrintService) cbServices.getSelectedItem();
cbResolutions.removeAllItems();
btnPrint.setEnabled(ps != null);
if (ps != null) {
PrinterResolution[] supportedResolutions = (PrinterResolution[])ps
.getSupportedAttributeValues(PrinterResolution.class, null, null);
if (supportedResolutions == null || supportedResolutions.length == 0) {
cbResolutions.addItem(new PrinterResolution(DEFAULT_DOCUMENT_DPI, DEFAULT_DOCUMENT_DPI, UNITS));
} else {
for (PrinterResolution pr : supportedResolutions) {
cbResolutions.addItem(pr);
}
}
}
}
});
for (PrintService ps : availablePrintServices) {
cbServices.addItem(ps);
}
lblServices.setLabelFor(cbServices);
btnPrint.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PrintService printService = (PrintService) cbServices.getSelectedItem();
PrinterResolution resolution = (PrinterResolution) cbResolutions.getSelectedItem();
if (printService != null && resolution != null) {
cbServices.setEnabled(false);
cbResolutions.setEnabled(false);
btnPrint.setEnabled(false);
frame.setVisible(false);
new PrintablePrintDPI(printService, resolution).test();
}
}
});
pnlMain.add(lblServices);
pnlMain.add(cbServices);
pnlMain.add(lblResolutions);
pnlMain.add(cbResolutions);
pnlMain.add(btnPrint);
frame.add(pnlMain);
frame.pack();
return frame;
}
private PrintablePrintDPI(PrintService printService, PrinterResolution printerResolution) {
this.printService = printService;
this.printerResolution = printerResolution;
}
private void test() {
System.out.printf("Perform test using %s and %dx%d DPI\n",
printService.getName(),
printerResolution.getCrossFeedResolution(UNITS),
printerResolution.getFeedResolution(UNITS));
PrinterJob job = PrinterJob.getPrinterJob();
try {
PrintRequestAttributeSet attributeSet = new HashPrintRequestAttributeSet();
attributeSet.add(printerResolution);
attributeSet.add(OrientationRequested.PORTRAIT);
job.setPrintService(printService);
job.setPrintable(this);
job.print(attributeSet);
} catch (PrinterException ex) {
throw new RuntimeException(ex);
}
}
@Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
if (pageIndex > 0) {
return NO_SUCH_PAGE;
}
final int[] deviceRes = printerResolution.getResolution(ResolutionSyntax.DPI);
Graphics2D g2 = (Graphics2D)graphics;
double hRes = g2.getTransform().getScaleX() * PAPER_DPI;
double vRes = g2.getTransform().getScaleY() * PAPER_DPI;
// Horizontal and vertical document resolution
g2.drawLine((int)pageFormat.getImageableX() + 5, (int)pageFormat.getImageableY() + 5,
(int)pageFormat.getImageableX() + 50, (int)pageFormat.getImageableY() + 5);
g2.drawString(Integer.toString((int)hRes),
(int)pageFormat.getImageableX() + 60,
(int)pageFormat.getImageableY() + 5 + g2.getFontMetrics().getHeight() / 2);
g2.drawLine((int)pageFormat.getImageableX() + 5, (int)pageFormat.getImageableY() + 5,
(int)pageFormat.getImageableX() + 5, (int)pageFormat.getImageableY() + 50);
g2.drawString(Integer.toString((int)vRes),
(int)pageFormat.getImageableX() + 5, (int)pageFormat.getImageableY() + 60);
String msg = String.format(
"Expected DPI: %dx%d, actual: %dx%d.\n",
deviceRes[0], deviceRes[1], (int)hRes, (int)vRes);
System.out.println(msg);
int msgX = (int)pageFormat.getImageableX() +
g2.getFontMetrics().stringWidth(Integer.toString((int)vRes)) + 20;
int msgY = (int)pageFormat.getImageableY() +
g2.getFontMetrics().getHeight() + 20;
g2.drawString(msg, msgX, msgY);
msgY += 20;
g2.drawString("ScaleX: " + g2.getTransform().getScaleX(), msgX, msgY);
msgY += 20;
g2.drawString("ScaleY: " + g2.getTransform().getScaleY(), msgX, msgY);
final float lineWidth = 0.2f;
double pageWidth = pageFormat.getWidth();
double xLeft = pageWidth / 10;
double yBase = pageFormat.getHeight() / 2;
double xBase = pageFormat.getWidth() / 2;
double yTop = yBase + 40;
double yBottom = pageFormat.getHeight() - pageFormat.getHeight() / 10;
g2.setStroke(new BasicStroke(lineWidth));
double xRight = pageWidth - xLeft;
g2.drawLine((int) xLeft, (int) yBase + 80,
(int) (xRight),(int) yBase + 80);
g2.drawLine((int) xBase, (int) yTop,
(int) (xBase),(int) yBottom);
GeneralPath line = new GeneralPath();
double halfLineWidth = lineWidth / 2.0f;
double yLine = yBase + 100;
double xLine = xBase + 20;
line.moveTo(xLeft, yLine);
line.lineTo(xLeft, yLine - halfLineWidth);
line.lineTo(xLine - halfLineWidth, yLine - halfLineWidth);
line.lineTo(xLine - halfLineWidth, yTop);
line.lineTo(xLine + halfLineWidth, yTop);
line.lineTo(xLine + halfLineWidth, yLine - halfLineWidth);
line.lineTo(xRight, yLine - halfLineWidth);
line.lineTo(xRight, yLine + halfLineWidth);
line.lineTo(xLine + halfLineWidth, yLine + halfLineWidth);
line.lineTo(xLine + halfLineWidth, yBottom);
line.lineTo(xLine - halfLineWidth, yBottom);
line.lineTo(xLine - halfLineWidth, yLine + halfLineWidth);
line.lineTo(xLeft, yLine + halfLineWidth );
line.closePath();
g2.clip(line);
g2.setColor(Color.RED);
line.reset();
line.moveTo(xLeft, yLine);
line.lineTo(xRight, yLine);
g2.draw(line);
line.reset();
line.moveTo(xBase + 20, yTop);
line.lineTo(xBase + 20, yBottom);
g2.draw(line);
return PAGE_EXISTS;
}
}