From 76a1109d6fc7baac9ebc7accff800ef8927931bb Mon Sep 17 00:00:00 2001 From: GennadiyKrivoshein <164895822+GennadiyKrivoshein@users.noreply.github.com> Date: Tue, 11 Nov 2025 03:49:39 +0000 Subject: [PATCH] 8251928: [macos] the printer DPI always be 72, cause some content lost when print out Reviewed-by: psadhukhan, prr --- .../lwawt/macosx/CPrinterGraphicsConfig.java | 18 +- .../classes/sun/lwawt/macosx/CPrinterJob.java | 46 ++- .../sun/lwawt/macosx/CPrinterSurfaceData.java | 7 +- .../native/libawt_lwawt/awt/PrinterView.m | 19 +- test/jdk/javax/print/PrintablePrintDPI.java | 300 ++++++++++++++++++ 5 files changed, 378 insertions(+), 12 deletions(-) create mode 100644 test/jdk/javax/print/PrintablePrintDPI.java diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterGraphicsConfig.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterGraphicsConfig.java index dcf8bbdfba5..069008ea50f 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterGraphicsConfig.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterGraphicsConfig.java @@ -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()); } } diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java index 11ac733be3a..979eeb36239 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java @@ -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 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; diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterSurfaceData.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterSurfaceData.java index bad5ecdcc09..cc14a185ed1 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterSurfaceData.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterSurfaceData.java @@ -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) { diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m index f39ca25a08f..d19948d9f0f 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m @@ -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, diff --git a/test/jdk/javax/print/PrintablePrintDPI.java b/test/jdk/javax/print/PrintablePrintDPI.java new file mode 100644 index 00000000000..6cae2be532c --- /dev/null +++ b/test/jdk/javax/print/PrintablePrintDPI.java @@ -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 cbResolutions = new JComboBox<>(); + cbResolutions.setRenderer(new ListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList 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 cbServices = new JComboBox<>(); + cbServices.setRenderer(new ListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList 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; + } +}