1 /*
   2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.print;
  27 
  28 import com.sun.javafx.print.PrintHelper;
  29 import javafx.beans.property.ReadOnlyObjectProperty;
  30 import javafx.beans.property.ReadOnlyObjectWrapper;
  31 import javafx.collections.ObservableSet;
  32 import javafx.geometry.Rectangle2D;
  33 
  34 import static javafx.print.PageOrientation.*;
  35 
  36 import com.sun.javafx.tk.PrintPipeline;
  37 import com.sun.javafx.print.PrinterImpl;
  38 import com.sun.javafx.print.Units;
  39 
  40 /**
  41  * A Printer instance represents the destination for a print job.
  42  * <p>
  43  * Printers may be enumerated and selected for use with a print job.
  44  * <p>
  45  * The configuration of the printer default settings are then used to
  46  * populate the initial settings for a job.
  47  * <p>
  48  * Since the availability of printers may change during the
  49  * execution of a program, due to administrative actions,
  50  * a long running program which has cached a printer which
  51  * has since been taken off-line, may create a job using that
  52  * instance, but printing will fail.
  53  *
  54  * @since JavaFX 8.0
  55  */
  56 public final class Printer {
  57 
  58     /**
  59      * Retrieve the installed printers.
  60      * The set of printers may be dynamic.
  61      * Consequently there is no guarantee that the result will be
  62      * the same from call to call, but should change only as
  63      * a result of the default changing in the environment of the
  64      * application.
  65      * <p>Note: since printers may be installed, but offline, then
  66      * the application may want to query the status of a printer
  67      * before using it.
  68      * @return may be null if there are no printers.
  69      * @throws SecurityException if the application does not
  70      * have permission to browse printers.
  71      */
  72     public static ObservableSet<Printer> getAllPrinters() {
  73         SecurityManager security = System.getSecurityManager();
  74         if (security != null) {
  75             security.checkPrintJobAccess();
  76         }
  77         return PrintPipeline.getPrintPipeline().getAllPrinters();
  78     }
  79 
  80     private static ReadOnlyObjectWrapper<Printer> defaultPrinter;
  81 
  82     private static ReadOnlyObjectWrapper<Printer> defaultPrinterImpl() {
  83         SecurityManager security = System.getSecurityManager();
  84         if (security != null) {
  85             security.checkPrintJobAccess();
  86         }
  87         if (defaultPrinter == null) {
  88             Printer p = PrintPipeline.getPrintPipeline().getDefaultPrinter();
  89             defaultPrinter =
  90                 new ReadOnlyObjectWrapper<Printer>(null, "defaultPrinter", p);
  91         }
  92         return defaultPrinter;
  93     }
  94 
  95     /**
  96      * A read only object property representing the current default printer.
  97      * If there are no installed printers, the wrapped value will be null.
  98      * @throws SecurityException if the application does not
  99      * have permission to browse printers.
 100      */
 101     public static ReadOnlyObjectProperty<Printer> defaultPrinterProperty() {
 102         return defaultPrinterImpl().getReadOnlyProperty();
 103     }
 104 
 105     /**
 106      * Retrieve the default printer.
 107      * May return null if no printers are installed.
 108      * <p>
 109      * The configuration of available printers may be dynamic.
 110      * Consequently there is no guarantee that the result will be
 111      * the same from call to call, but should change only as
 112      * a result of the default changing in the environment of the
 113      * application.
 114      * @return default printer or null.
 115      * @throws SecurityException if the application does not
 116      * have permission to browse printers.
 117      */
 118     public static Printer getDefaultPrinter() {
 119         return defaultPrinterProperty().get();
 120     }
 121 
 122     private PrinterImpl impl;
 123 
 124     Printer(PrinterImpl impl) {
 125         this.impl = impl;
 126         impl.setPrinter(this);
 127     }
 128 
 129     PrinterImpl getPrinterImpl() {
 130         return impl;
 131     }
 132 
 133     /**
 134      * Return the name used by the underlying system to identify
 135      * the printer to users and/or applications.
 136      * @return printer name.
 137      */
 138     public String getName() {
 139         return impl.getName();
 140     }
 141 
 142     private PrinterAttributes attributes;
 143     /**
 144      * Retrieves the delegate object encapsulating the printer
 145      * attributes and capabilities.
 146      * @return printer attributes.
 147      */
 148     public PrinterAttributes getPrinterAttributes() {
 149         if (attributes == null) {
 150             attributes = new PrinterAttributes(impl);
 151         }
 152         return attributes;
 153     }
 154 
 155     /**
 156      * Returns the default settings for this printer as would be
 157      * used in setting up a job for this printer.
 158      * <p>
 159      * When a job is first created and associated with a printer,
 160      * (typically the default printer), it acquires these default
 161      * settings for that printer.
 162      * This method always returns a new instance.
 163      */
 164     JobSettings getDefaultJobSettings() {
 165         return impl.getDefaultJobSettings();
 166     }
 167 
 168     /**
 169      * The MarginType is used to determine the printable area of a PageLayout.
 170      * @since JavaFX 8.0
 171      */
 172     public static enum MarginType {
 173         /**
 174          * This requests a default 0.75 inch margin on all sides.
 175          * This is considered to be a common default and is supported
 176          * by all known printers. However this may be adjusted if the paper is
 177          * too small, to ensure that the margins are not more than 50% of
 178          * the smaller dimension. Applications that do expect to deal with
 179          * such small media should likely be specifying the required margins
 180          * explicitly.
 181          * In the unlikely event the hardware margin is larger than 0.75"
 182          * it will be adjusted to that same hardware minimum on all sides.
 183          */
 184         DEFAULT,
 185         /**
 186          * Request margins are set to be the smallest on each side that
 187          * the hardware allows. This creates the greatest printable area
 188          * but the margins may not be aesthetic if they are too small, or
 189          * there is significant variation on the different sides of the
 190          * paper.
 191          * <p>
 192          * This is is also useful for an application that wants to know
 193          * this so it can construct a new PageLayout that fits within
 194          * these margins.
 195          */
 196         HARDWARE_MINIMUM,
 197         /**
 198          * Choose the largest of the four hardware margins, and use that for
 199          * all for margins, so that the margins are equal on all sides.
 200          */
 201         EQUAL,
 202         /**
 203          * Similar to <code>EQUAL</code>, but it chooses the larger of
 204          * the left/right hardware margins and top/bottom hardware margins
 205          * separately, so that the top and bottom margins are equal, and
 206          * the left and right margins are equal.
 207          */
 208         EQUAL_OPPOSITES,
 209 
 210     };
 211 
 212     private PageLayout defPageLayout;
 213     /**
 214      * Return the default page layout for this printer.
 215      * @return default page layout.
 216      */
 217     public PageLayout getDefaultPageLayout() {
 218         if (defPageLayout == null) {
 219             PrinterAttributes printerCaps = getPrinterAttributes();
 220             defPageLayout =
 221                 createPageLayout(printerCaps.getDefaultPaper(),
 222                                  printerCaps.getDefaultPageOrientation(),
 223                                  MarginType.DEFAULT);
 224         }
 225         return defPageLayout;
 226     }
 227 
 228     /**
 229      * Obtain a new PageLayout instance for this printer using the specified
 230      * parameters.
 231      * The paper should be one of the supported papers and
 232      * the orientation should be a supported orientation.
 233      * If the printer cannot support the layout as specified, it
 234      * will adjust the returned layout to a supported configuration
 235      * @param paper The paper to use
 236      * @param orient The orientation to use
 237      * @param mType the margin type to use
 238      * @return PageLayout based on the specified parameters.
 239      * @throws NullPointerException if any of the parameters are null.
 240      */
 241     public PageLayout createPageLayout(Paper paper, PageOrientation orient,
 242                         MarginType mType) {
 243 
 244         if (paper == null || orient == null || mType == null) {
 245             throw new NullPointerException("Parameters cannot be null");
 246         }
 247 
 248         // TBD: Adjust paper to a supported one first.
 249         Rectangle2D imgArea = impl.printableArea(paper);
 250         double width = paper.getWidth() / 72.0;
 251         double height = paper.getHeight() / 72.0;
 252         double plm = imgArea.getMinX();
 253         double ptm = imgArea.getMinY();
 254         double prm = width - imgArea.getMaxX();
 255         double pbm = height - imgArea.getMaxY();
 256 
 257         switch (mType) {
 258         case DEFAULT:
 259             plm = (plm <= 0.75) ? 0.75 : plm;
 260             prm = (prm <= 0.75) ? 0.75 : prm;
 261             ptm = (ptm <= 0.75) ? 0.75 : ptm;
 262             pbm = (pbm <= 0.75) ? 0.75 : pbm;
 263             break;
 264         case EQUAL: {
 265             double maxH = (double)Math.max(plm, prm);
 266             double maxV = (double)Math.max(ptm, pbm);
 267             double maxM = (double)Math.max(maxH, maxV);
 268             plm = prm = ptm = pbm = maxM;
 269             break;
 270         }
 271         case EQUAL_OPPOSITES: {
 272             double maxH = (double)Math.max(plm, prm);
 273             double maxV = (double)Math.max(ptm, pbm);
 274             plm = prm = maxH;
 275             ptm = pbm = maxV;
 276             break;
 277         }
 278         case HARDWARE_MINIMUM:
 279         default: // Use hardware margins as is.
 280             break;
 281         }
 282 
 283         double lm, rm, tm, bm;
 284         // Now we gave the margins, they need to be adjusted into
 285         // the orientation of the paper. If the orientation is not
 286         // supported by the printer, then that needs to adjusted first.
 287 
 288         // TBD: Adjust orient to a supported one first.
 289         switch (orient) {
 290         case LANDSCAPE: lm = pbm; rm = ptm; tm = plm; bm = prm;
 291             break;
 292         case REVERSE_LANDSCAPE: lm = ptm; rm = pbm; tm = prm; bm = plm;
 293             break;
 294         case REVERSE_PORTRAIT: lm = prm; rm = plm; tm = pbm; bm = ptm;
 295             break;
 296         default: lm = plm; rm = prm; tm = ptm; bm = pbm;
 297         }
 298         lm *= 72;
 299         rm *= 72;
 300         tm *= 72;
 301         bm *= 72;
 302         return new PageLayout(paper, orient, lm, rm, tm, bm);
 303     }
 304 
 305     /**
 306      * Obtain a new PageLayout for this printer using the specified
 307      * parameters.
 308      * The paper should be one of the supported papers and
 309      * the orientation should be a supported orientation.
 310      * <p>
 311      * Margin values are specified in 1/72 of an inch points.
 312      * Margins will be validated against the printer supported margins,
 313      * and adjusted if necessary. This method is generally useful to
 314      * a client that wants margins that are different (eg wider)
 315      * than the default margins, such as 1" at top and bottom and
 316      * 0.5" to the left and right.
 317      * <p>A client that needs to know what margin values are legal should first
 318      * obtain a PageLayout using the <code>HARDWARE_MINIMUM</code> margins.
 319      * <p>
 320      * If the printer cannot support the layout as specified, it
 321      * will adjust the returned layout to a supported configuration
 322      * @param paper The paper to use
 323      * @param orient The orientation to use
 324      * @param lMargin the left margin to use in pts.
 325      * @param rMargin the right margin to use in pts.
 326      * @param tMargin the top margin to use in pts.
 327      * @param bMargin the bottom margin to use in pts.
 328      * @return PageLayout based on the specified parameters.
 329      * @throws NullPointerException if paper or orient are null.
 330      * @throws IllegalArgumentException if any of the margins values are
 331      * less than zero.
 332      */
 333     public PageLayout createPageLayout(Paper paper, PageOrientation orient,
 334                                        double lMargin, double rMargin,
 335                                        double tMargin, double bMargin) {
 336 
 337         if (paper == null || orient == null) {
 338             throw new NullPointerException("Parameters cannot be null");
 339         }
 340         if (lMargin < 0 || rMargin < 0 || tMargin < 0 || bMargin < 0) {
 341             throw new IllegalArgumentException("Margins must be >= 0");
 342         }
 343         // TBD: Adjust paper to a supported one first.
 344         Rectangle2D imgArea = impl.printableArea(paper);
 345         double width = paper.getWidth() / 72.0;
 346         double height = paper.getHeight() / 72.0;
 347         double plm = imgArea.getMinX();
 348         double ptm = imgArea.getMinY();
 349         double prm = width - imgArea.getMaxX();
 350         double pbm = height - imgArea.getMaxY();
 351 
 352         // Check if the requested margins exceed the paper and
 353         // if they do, ignore them.
 354         boolean useDefault = false;
 355         if (orient == PORTRAIT || orient == REVERSE_PORTRAIT) {
 356             if ((lMargin + rMargin > width) ||
 357                 (tMargin + bMargin > height)) {
 358                 useDefault = true;
 359             }
 360         } else {
 361             if ((lMargin + rMargin > height) ||
 362                 (tMargin + bMargin > width)) {
 363                 useDefault = true;
 364             }
 365         }
 366         if (useDefault) {
 367             return createPageLayout(paper, orient, MarginType.DEFAULT);
 368         }
 369 
 370         double lm, rm, tm, bm;
 371         // TBD: Adjust orient to a supported one first.
 372         switch (orient) {
 373         case LANDSCAPE: lm = pbm; rm = ptm; tm = plm; bm = prm;
 374             break;
 375         case REVERSE_LANDSCAPE: lm = ptm; rm = pbm; tm = prm; bm = plm;
 376             break;
 377         case REVERSE_PORTRAIT: lm = prm; rm = plm; tm = pbm; bm = ptm;
 378             break;
 379         default: lm = plm; rm = prm; tm = ptm; bm = pbm;
 380         }
 381 
 382         lm = (lMargin <= lm) ? lMargin : lm;
 383         rm = (rMargin <= rm) ? rMargin : rm;
 384         tm = (tMargin <= tm) ? tMargin : tm;
 385         bm = (bMargin <= bm) ? bMargin : bm;
 386 
 387         return new PageLayout(paper, orient, lm, rm, tm, bm);
 388     }
 389 
 390     @Override public String toString() {
 391         return "Printer " + getName();
 392     }
 393 
 394     static {
 395         // This is used by classes in different packages to get access to
 396         // private and package private methods.
 397         PrintHelper.setPrintAccessor(new PrintHelper.PrintAccessor() {
 398 
 399             @Override
 400             public PrintResolution createPrintResolution(int fr, int cfr) {
 401                 return new PrintResolution(fr, cfr);
 402             }
 403 
 404             @Override
 405             public Paper createPaper(String paperName,
 406                                      double paperWidth,
 407                                      double paperHeight,
 408                                      Units units) {
 409                 return new Paper(paperName, paperWidth, paperHeight, units);
 410             }
 411 
 412             @Override
 413             public PaperSource createPaperSource(String name) {
 414                 return new PaperSource(name);
 415             }
 416 
 417             @Override
 418             public JobSettings createJobSettings(Printer printer) {
 419                 return new JobSettings(printer);
 420             }
 421 
 422             /**
 423              * PrintAccess is used so that implementation code outside this package
 424              * package can construct printer instances using a non-visible API.
 425              * We need this since its not valid for applications to create
 426              * Printer instances, and we also need to pass in the delegate
 427              * impl object which is not intended to be public.
 428              */
 429             @Override
 430             public Printer createPrinter(PrinterImpl impl) {
 431                 return new Printer(impl);
 432             }
 433 
 434             @Override
 435             public PrinterImpl getPrinterImpl(Printer printer) {
 436                 return printer.getPrinterImpl();
 437             }
 438         });
 439     }
 440 }