diff --git a/src/java.desktop/unix/native/common/awt/CUPSfuncs.c b/src/java.desktop/unix/native/common/awt/CUPSfuncs.c index 7594bd2c62a..7c60d824067 100644 --- a/src/java.desktop/unix/native/common/awt/CUPSfuncs.c +++ b/src/java.desktop/unix/native/common/awt/CUPSfuncs.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -574,10 +574,10 @@ Java_sun_print_CUPSPrinter_getPageSizes(JNIEnv *env, // paper width and height dims[i*6] = size->width; dims[(i*6)+1] = size->length; - // paper printable area + // paper printable area. x and y coordinates of the lower left corner, width and height dims[(i*6)+2] = size->left; - dims[(i*6)+3] = size->top; - dims[(i*6)+4] = size->right; + dims[(i*6)+3] = size->top - size->bottom; + dims[(i*6)+4] = size->right - size->left; dims[(i*6)+5] = size->bottom; } } diff --git a/test/jdk/javax/print/CUPSPrinterImageableAreaTest.java b/test/jdk/javax/print/CUPSPrinterImageableAreaTest.java new file mode 100644 index 00000000000..b07cc39c6ff --- /dev/null +++ b/test/jdk/javax/print/CUPSPrinterImageableAreaTest.java @@ -0,0 +1,338 @@ +/* + * 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 java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Graphics; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.print.PageFormat; +import java.awt.print.Paper; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.util.ArrayList; +import java.util.List; +import javax.print.PrintService; +import javax.print.PrintServiceLookup; +import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.PrintRequestAttributeSet; +import javax.print.attribute.Size2DSyntax; +import javax.print.attribute.standard.Media; +import javax.print.attribute.standard.MediaPrintableArea; +import javax.print.attribute.standard.MediaSize; +import javax.print.attribute.standard.MediaSizeName; +import javax.swing.DefaultComboBoxModel; +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.SwingConstants; +import javax.swing.plaf.basic.BasicComboBoxRenderer; + +/* + * @test + * @bug 8344119 + * @key printer + * @requires (os.family == "linux" | os.family == "mac") + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @summary CUPSPrinter imageable area + * @run main/manual CUPSPrinterImageableAreaTest + */ + +public class CUPSPrinterImageableAreaTest { + + private static final List ALLOWED_MEDIA_LIST = List.of(MediaSizeName.ISO_A4, MediaSizeName.NA_LETTER); + private static final double DPI = 72.0; + private static final double MM_PER_INCH = 2.54; + private static final String INSTRUCTIONS = """ + +
+ The test checks that the media margins fetched from the printer's PPD file are correct.
+ Press the 'Print sample' button to print a test page.
+ Required paper size and expected margins will be shown on the print dialog. + A passing test will print the page with a black rectangle along the printable area. + Ensure that all sides of the rectangle are printed.
+ Click 'Pass' button, or click 'Fail' button if the test failed. +
+ + """; + private static final String PAPER_INSTRUCTIONS_FORMAT = """ + + Required paper size: + Expected margins: + """; + + public static void main(String[] args) throws Exception { + PassFailJFrame.builder() + .instructions(INSTRUCTIONS) + .testUI(createTestUI()) + .columns(55) + .build() + .awaitAndCheck(); + } + + private static JFrame createTestUI() { + final TestServiceData[] testServiceList = getTestServiceList(); + if (testServiceList.length == 0) { + throw new RuntimeException("Print services support borderless print only"); + } + + final JFrame frame = new JFrame("CUPS Printer imageable area test"); + JPanel pnlRoot = new JPanel(); + JLabel lblPrintServices = new JLabel("Select a print service for the test"); + JComboBox cbPrintServices = new JComboBox<>(); + JPanel pnlInstruction = new JPanel(); + JLabel lblInstruction = new JLabel(); + JButton btnPrint = new JButton("Print sample"); + + lblPrintServices.setLabelFor(cbPrintServices); + lblPrintServices.setAlignmentX(SwingConstants.LEFT); + + lblInstruction.setPreferredSize(new Dimension(250, 150)); + pnlInstruction.setBackground(Color.white); + pnlInstruction.add(lblInstruction); + + cbPrintServices.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int selectedIndex = cbPrintServices.getSelectedIndex(); + if (selectedIndex < 0) { + lblInstruction.setText(""); + btnPrint.setEnabled(false); + return; + } + + TestServiceData testServiceData = testServiceList[selectedIndex]; + PageFormat pageFormat = testServiceData.pageFormat; + // margins: left, bottom, right, top + double[] margins = new double[]{ + pageFormat.getImageableX(), + pageFormat.getHeight() - pageFormat.getImageableHeight() - pageFormat.getImageableY(), + pageFormat.getWidth() - pageFormat.getImageableWidth() - pageFormat.getImageableX(), + pageFormat.getImageableY() + }; + String printServiceInstructions = PAPER_INSTRUCTIONS_FORMAT.formatted( + testServiceData.mediaSizeName.toString(), inchesToMM(margins[0]), + inchesToMM(margins[1]), inchesToMM(margins[2]), inchesToMM(margins[3])); + lblInstruction.setText(printServiceInstructions); + btnPrint.setEnabled(true); + } + }); + + DefaultComboBoxModel model = new DefaultComboBoxModel<>(); + for(TestServiceData tsd : testServiceList) { + model.addElement(tsd.printService.getName()); + } + cbPrintServices.setModel(model); + cbPrintServices.setSelectedIndex(-1); + PrintService defaultPrintService = PrintServiceLookup.lookupDefaultPrintService(); + if (defaultPrintService != null && model.getIndexOf(defaultPrintService.getName()) >= 0) { + cbPrintServices.setSelectedItem(defaultPrintService.getName()); + } else { + cbPrintServices.setSelectedIndex(0); + } + + btnPrint.setPreferredSize(new Dimension(200, 80)); + btnPrint.addActionListener((e) -> { + int selectedIndex = cbPrintServices.getSelectedIndex(); + if (selectedIndex < 0) { + return; + } + btnPrint.setEnabled(false); + cbPrintServices.setEnabled(false); + TestServiceData testServiceData = testServiceList[selectedIndex]; + PrinterJob job = PrinterJob.getPrinterJob(); + try { + job.setPrintService(testServiceData.printService); + job.setPrintable(new RectPrintable(), testServiceData.pageFormat); + job.print(); + } catch (PrinterException ex) { + throw new RuntimeException(ex); + } + }); + + LayoutManager layout = new GridBagLayout(); + pnlRoot.setLayout(layout); + + addGridBagComponent(pnlRoot, lblPrintServices, 0); + addGridBagComponent(pnlRoot, cbPrintServices, 1); + addGridBagComponent(pnlRoot, pnlInstruction, 2); + addGridBagComponent(pnlRoot, btnPrint, 3); + + frame.add(pnlRoot); + frame.pack(); + frame.setResizable(false); + return frame; + } + + private static TestServiceData[] getTestServiceList() { + PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null); + if (printServices == null || printServices.length == 0) { + throw new RuntimeException("Print services not found"); + } + + List testServiceList = new ArrayList<>(); + for (PrintService ps : printServices) { + try { + MediaSizeName msn = getTestMediaSizeName(ps); + PageFormat pf = createTestPageFormat(msn, ps); + testServiceList.add(new TestServiceData(ps, msn, pf)); + } catch (Exception ignore) { //in case if can't create required PageFormat + } + } + return testServiceList.toArray(TestServiceData[]::new); + } + + private static MediaSizeName getTestMediaSizeName(PrintService printService) { + //Use printer's default media or one of the alloed medias + Media testMedia = (Media) printService.getDefaultAttributeValue(Media.class); + if (testMedia == null) { + Media[] medias = (Media[]) printService + .getSupportedAttributeValues(Media.class, null, null); + if (medias == null || medias.length == 0) { + throw new RuntimeException("Medias not found"); + } + for (Media media : medias) { + if (ALLOWED_MEDIA_LIST.contains(media)) { + testMedia = media; + break; + } + } + } + if (!(testMedia instanceof MediaSizeName)) { + throw new RuntimeException("Test media not found"); + } + return (MediaSizeName) testMedia; + } + + private static PageFormat createTestPageFormat(MediaSizeName testMedia, PrintService printService) { + MediaSize ms = MediaSize.getMediaSizeForName(testMedia); + if (ms == null) { + throw new RuntimeException("Media size not defined"); + } + + MediaPrintableArea mpa = getMaximumMediaPrintableArea(testMedia, ms, printService); + if (mpa == null) { + throw new RuntimeException("Media printable area not defined"); + } + + PageFormat pageFormat = new PageFormat(); + pageFormat.setOrientation(PageFormat.PORTRAIT); + Paper paper = new Paper(); + paper.setSize(ms.getX(MediaSize.INCH) * DPI, ms.getY(MediaSize.INCH) * DPI); + paper.setImageableArea(mpa.getX(MediaPrintableArea.INCH) * DPI, + mpa.getY(MediaPrintableArea.INCH) * DPI, + mpa.getWidth(MediaPrintableArea.INCH) * DPI, + mpa.getHeight(MediaPrintableArea.INCH) * DPI); + pageFormat.setPaper(paper); + return pageFormat; + } + + private static MediaPrintableArea getMaximumMediaPrintableArea(MediaSizeName msn, MediaSize ms, + PrintService printService) { + final float paperSizeX = ms.getX(Size2DSyntax.MM); + final float paperSizeY = ms.getY(Size2DSyntax.MM); + final float sizeDev = 0.2f; + + PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet(); + attrs.add(msn); + MediaPrintableArea[] mpas = (MediaPrintableArea[]) printService + .getSupportedAttributeValues(MediaPrintableArea.class, null, attrs); + if (mpas == null || mpas.length == 0) { + throw new RuntimeException("Printable area not found"); + } + + MediaPrintableArea mpa = null; + for (MediaPrintableArea area : mpas) { + float mpaSize = area.getWidth(MediaPrintableArea.MM) * area.getHeight(MediaPrintableArea.MM); + //do not use borderless printable area + if (sizeDev >= Math.abs(paperSizeX - area.getWidth(MediaPrintableArea.MM)) && + sizeDev >= Math.abs(paperSizeY - area.getHeight(MediaPrintableArea.MM))) { + continue; + } + if (mpa == null) { + mpa = area; + } else if (mpaSize > (area.getWidth(MediaPrintableArea.MM) * area.getHeight(MediaPrintableArea.MM))) { + mpa = area; + } + } + return mpa; + } + + private static double inchesToMM(double inches) { + return inches / MM_PER_INCH; + } + + private static void addGridBagComponent(JPanel p, Component c, int y) { + GridBagConstraints constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets = new Insets(4, 4, 4, 4); + constraints.gridx = 0; + constraints.gridy = y; + p.add(c, constraints); + } + + private static class RectPrintable implements Printable { + + @Override + public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException { + if (pageIndex == 0) { + Graphics2D g = (Graphics2D) graphics; + g.setStroke(new BasicStroke(3)); + g.drawRect((int) pageFormat.getImageableX(), (int) pageFormat.getImageableY(), + (int) pageFormat.getImageableWidth(), (int) pageFormat.getImageableHeight()); + return PAGE_EXISTS; + } + return NO_SUCH_PAGE; + } + } + + private static class TestServiceData { + + final PrintService printService; + final MediaSizeName mediaSizeName; + final PageFormat pageFormat; + + private TestServiceData(PrintService printService, MediaSizeName mediaSizeName, PageFormat pageFormat) { + this.printService = printService; + this.mediaSizeName = mediaSizeName; + this.pageFormat = pageFormat; + } + } +}