001package jmri.util;
002
003import javax.print.PrintService;
004import javax.print.PrintServiceLookup;
005import javax.print.attribute.standard.Media;
006import javax.print.attribute.standard.MediaSize;
007import javax.print.attribute.standard.MediaSizeName;
008import javax.print.attribute.standard.OrientationRequested;
009
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import java.awt.Dimension;
014import java.awt.PageAttributes;
015import java.awt.PageAttributes.MediaType;
016import java.util.Locale;
017import java.util.Set;
018
019public class PaperUtils {
020    // Countries primarily using US Letter
021    private static final Set<String> LETTER_COUNTRIES = Set.of("US", "CA", "MX", "PH", "PR");
022
023    private static Dimension cachedPaperSize = null;
024
025    private static final int pointsPerInch = 72;
026
027    /**
028     * Returns the default paper size as a Dimension object in points
029     *
030     * @return The Dimension object representing the default paper size in
031     *         points
032     */
033    public static Dimension getPaperSizeDimension() {
034        if (cachedPaperSize == null) {
035            cachedPaperSize = getAutomaticPaperSize();
036        }
037        return cachedPaperSize;
038    }
039
040    /**
041     * Retrieves the default printer's paper size in points (1/72"), adjusted
042     * for the current default orientation. If this is not possible, returns
043     * null.
044     * 
045     * @return The Dimension object representing the default paper size in
046     *         points, or null if it is not possible to retrieve the paper size.
047     */
048    public static Dimension getOrientedPaperSizeInPointsIfPossible() {
049        PrintService service = PrintServiceLookup.lookupDefaultPrintService();
050        if (service == null) {
051            log.warn("No default printer found");
052            return null;
053        }
054
055        // Get Physical Paper Dimensions
056        Media mediaName = (MediaSizeName) service.getDefaultAttributeValue(Media.class);
057
058        // Handle missing default media name
059        if (mediaName == null) {
060            Object supported = service.getSupportedAttributeValues(Media.class, null, null);
061            if (supported instanceof Media[] && ((Media[]) supported).length > 0) {
062                mediaName = ((Media[]) supported)[0];
063            }
064        }
065
066        if (!(mediaName instanceof MediaSizeName)) {
067            return null;
068        }
069
070        MediaSize size = MediaSize.getMediaSizeForName((MediaSizeName) mediaName);
071
072        if (size == null) {
073            return null;
074        }
075
076        int finalWidth = (int) (size.getX(MediaSize.INCH) * 72);
077        int finalHeight = (int) (size.getY(MediaSize.INCH) * 72);
078
079        // Get Orientation and Swap if necessary
080        OrientationRequested orient =
081                (OrientationRequested) service.getDefaultAttributeValue(OrientationRequested.class);
082
083        // LANDSCAPE and REVERSE_LANDSCAPE mean we swap width and height
084        if (orient == OrientationRequested.LANDSCAPE || orient == OrientationRequested.REVERSE_LANDSCAPE) {
085            int temp = finalWidth;
086            finalWidth = finalHeight;
087            finalHeight = temp;
088        }
089
090        return new Dimension(finalWidth, finalHeight);
091    }
092
093    /**
094     * Returns the default paper size as a PaperSize enum. This interrogates the
095     * default printer and may be slow if the printer is not available. You
096     * should probably use {@link #getPaperSizeDimension()} instead as it caches
097     * the result.
098     * 
099     * @return A Dimension object representing the default paper size in points.
100     */
101    public static Dimension getAutomaticPaperSize() {
102        // Try Printer Discovery
103        Dimension size = getOrientedPaperSizeInPointsIfPossible();
104
105        if (size != null) {
106            return size;
107        }
108
109        // Fallback to System Locale
110        String country = Locale.getDefault().getCountry().toUpperCase();
111        if (LETTER_COUNTRIES.contains(country)) {
112            return new Dimension((int) (8.5 * pointsPerInch), (int) (11.0 * pointsPerInch));
113        }
114
115        // Final Default (Global Standard)
116        return new Dimension((int) (8.27 * pointsPerInch), (int) (11.69 * pointsPerInch));
117    }
118
119    /**
120     * Syncs the PageAttributes object to the default printer's settings.
121     * 
122     * @param pageAttr The PageAttributes object to sync.
123     */
124    public static void syncPageAttributesToPrinter(PageAttributes pageAttr) {
125        PrintService service = PrintServiceLookup.lookupDefaultPrintService();
126        if (service != null) {
127            Media media = (Media) service.getDefaultAttributeValue(Media.class);
128
129            if (media != null && media instanceof MediaSizeName) {
130                // Map the javax.print MediaSizeName to AWT PageAttributes.MediaType
131                if (media.equals(MediaSizeName.NA_LETTER)) {
132                    pageAttr.setMedia(MediaType.NA_LETTER);
133                } else if (media.equals(MediaSizeName.NA_LEGAL)) {
134                    pageAttr.setMedia(MediaType.NA_LEGAL);
135                } else if (media.equals(MediaSizeName.ISO_A4)) {
136                    pageAttr.setMedia(MediaType.ISO_A4);
137                } else {
138                    log.warn("Unsupported media: {}", media);
139                }
140            }
141        } else {
142            log.warn("No default printer found");
143        }
144    }
145
146    private final static Logger log = LoggerFactory.getLogger(PaperUtils.class);
147
148}