All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.prism.j2d.print.J2DPrinterJob Maven / Gradle / Ivy

There is a newer version: 24-ea+15
Show newest version
/*
 * Copyright (c) 2013, 2022, 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
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.
 */

package com.sun.prism.j2d.print;

import javafx.print.Collation;
import javafx.print.JobSettings;
import javafx.print.PageLayout;
import javafx.print.PageOrientation;
import javafx.print.PageRange;
import javafx.print.Paper;
import javafx.print.PaperSource;
import javafx.print.PrintColor;
import javafx.print.PrintResolution;
import javafx.print.PrintSides;
import javafx.print.Printer;
import javafx.print.Printer.MarginType;
import javafx.print.PrinterAttributes;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Window;
import javax.print.PrintService;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttribute;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.ResolutionSyntax;
import javax.print.attribute.Size2DSyntax;
import javax.print.attribute.standard.Chromaticity;
import javax.print.attribute.standard.Copies;
import javax.print.attribute.standard.Destination;
import javax.print.attribute.standard.DialogTypeSelection;
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.print.attribute.standard.MediaTray;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.PageRanges;
import javax.print.attribute.standard.PrintQuality;
import javax.print.attribute.standard.PrinterResolution;
import javax.print.attribute.standard.SheetCollate;
import javax.print.attribute.standard.Sides;
import java.awt.*;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Set;
import com.sun.glass.ui.Application;
import com.sun.javafx.PlatformUtil;
import com.sun.javafx.print.PrintHelper;
import com.sun.javafx.print.PrinterImpl;
import com.sun.javafx.print.PrinterJobImpl;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.stage.WindowHelper;
import com.sun.javafx.tk.TKStage;
import com.sun.javafx.tk.Toolkit;
import com.sun.glass.utils.NativeLibLoader;
import com.sun.prism.impl.PrismSettings;

import com.sun.prism.j2d.PrismPrintGraphics;

import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.security.PrivilegedAction;

public class J2DPrinterJob implements PrinterJobImpl {

    static {
        @SuppressWarnings("removal")
        var dummy = AccessController.doPrivileged((PrivilegedAction) () -> {
            String libName = "prism_common";

            if (PrismSettings.verbose) {
                System.out.println("Loading Prism common native library ...");
            }
            NativeLibLoader.loadLibrary(libName);
            if (PrismSettings.verbose) {
                System.out.println("\tsucceeded.");
            }
            return null;
        });
    }

    javafx.print.PrinterJob fxPrinterJob;
    java.awt.print.PrinterJob pJob2D;
    javafx.print.Printer fxPrinter;
    J2DPrinter j2dPrinter;

    private JobSettings settings;
    private PrintRequestAttributeSet printReqAttrSet;
    private volatile Object elo = null;

    private static Class onTopClass = null;
    @SuppressWarnings("removal")
    PrintRequestAttribute getAlwaysOnTop(final long id) {
        return AccessController.doPrivileged(
            (PrivilegedAction) () -> {

            PrintRequestAttribute alwaysOnTop = null;
            try {
                if (onTopClass == null) {
                    onTopClass =
                        Class.forName("javax.print.attribute.standard.DialogOwner");
                }
                if (id == 0) {
                    Constructor
                         cons = onTopClass.getConstructor();
                    alwaysOnTop = cons.newInstance();
                } else {
                    alwaysOnTop = getAlwaysOnTop(onTopClass, id);
                }
            } catch (Throwable t) {
            }
            return alwaysOnTop;
        });
    }

    private static native
        PrintRequestAttribute getAlwaysOnTop(Class onTopClass, long id);

    public J2DPrinterJob(javafx.print.PrinterJob fxJob) {

        fxPrinterJob = fxJob;
        fxPrinter = fxPrinterJob.getPrinter();
        j2dPrinter = getJ2DPrinter(fxPrinter);
        settings = fxPrinterJob.getJobSettings();
        pJob2D = java.awt.print.PrinterJob.getPrinterJob();
        try {
            pJob2D.setPrintService(j2dPrinter.getService());
        } catch (PrinterException pe) {
        }
        printReqAttrSet = new HashPrintRequestAttributeSet();
        printReqAttrSet.add(DialogTypeSelection.NATIVE);
        j2dPageable = new J2DPageable();
        pJob2D.setPageable(j2dPageable);
    }

    private void setEnabledState(Window owner, boolean state) {
        if (owner == null) {
           return;
        }
        final TKStage stage = WindowHelper.getPeer(owner);
        if (stage == null) { // just in case.
            return;
        }
        Application.invokeAndWait(() -> stage.setEnabled(state));
    }

    @Override
    public boolean showPrintDialog(Window owner) {

        if (jobRunning || jobDone || jobCanceled) {
            return false;
        }

        if (GraphicsEnvironment.isHeadless()) {
            return true;
        }

        if (onTopClass != null) {
            printReqAttrSet.remove(onTopClass);
        }
        if (owner != null) {
            long id = 0L;
            if (PlatformUtil.isWindows()) {
                id = WindowHelper.getPeer(owner).getRawHandle();
            }
            PrintRequestAttribute alwaysOnTop = getAlwaysOnTop(id);
            if (alwaysOnTop != null) {
                printReqAttrSet.add(alwaysOnTop);
            }
        }

        boolean rv = false;
        syncSettingsToAttributes();
        try {
            setEnabledState(owner, false);
            if (!Toolkit.getToolkit().isFxUserThread()) {
                rv = pJob2D.printDialog(printReqAttrSet);
            } else {
                // If we are on the event thread, we need to check whether
                // we are allowed to call a nested event handler.
                if (!Toolkit.getToolkit().canStartNestedEventLoop()) {
                    throw new IllegalStateException(
              "Printing is not allowed during animation or layout processing");
                }
                rv = showPrintDialogWithNestedLoop(owner);
            }
            if (rv) {
                updateSettingsFromDialog();
            }
        } finally {
            setEnabledState(owner, true);
        }
        return rv;
    }

    private class PrintDialogRunnable implements Runnable {

        @Override
        public void run() {
            boolean rv = false;
            try {
                rv = pJob2D.printDialog(printReqAttrSet);
            } catch (Exception e) {
            } finally {
                Application.invokeLater(new ExitLoopRunnable(this, rv));
            }
        }
    }

    private boolean showPrintDialogWithNestedLoop(Window owner) {
        PrintDialogRunnable dr = new PrintDialogRunnable();
        Thread prtThread = new Thread(dr, "FX Print Dialog Thread");
        prtThread.start();
        // the nested event loop will return after the runnable exits.
        Object rv = Toolkit.getToolkit().enterNestedEventLoop(dr);

        boolean rvbool = false;
        try {
            rvbool = ((Boolean)rv).booleanValue();
        } catch (Exception e) {
        }
        return rvbool;
    }

    @Override
    public boolean showPageDialog(Window owner) {
        if (jobRunning || jobDone || jobCanceled) {
            return false;
        }
        if (GraphicsEnvironment.isHeadless()) {
            return true;
        }

        if (onTopClass != null) {
            printReqAttrSet.remove(onTopClass);
        }
        if (owner != null) {
            long id = 0L;
            if (PlatformUtil.isWindows()) {
                id = WindowHelper.getPeer(owner).getRawHandle();
            }
            PrintRequestAttribute alwaysOnTop = getAlwaysOnTop(id);
            if (alwaysOnTop != null) {
                printReqAttrSet.add(alwaysOnTop);
            }
        }

        boolean rv = false;
        syncSettingsToAttributes();
        try {
            setEnabledState(owner, false);
            if (!Toolkit.getToolkit().isFxUserThread()) {
                PageFormat pf = pJob2D.pageDialog(printReqAttrSet);
                rv = pf != null;
            } else {
                // If we are on the event thread, we need to check whether
                // we are allowed to call a nested event handler.
                if (!Toolkit.getToolkit().canStartNestedEventLoop()) {
                    throw new IllegalStateException(
               "Printing is not allowed during animation or layout processing");
                }
                rv = showPageDialogFromNestedLoop(owner);
            }
        } finally {
            setEnabledState(owner, true);
        }
        if (rv) {
            updateSettingsFromDialog();
        }
        return rv;
    }

    private class PageDialogRunnable implements Runnable {

        @Override
        public void run() {
            PageFormat pf = null;
            try {
                pf = pJob2D.pageDialog(printReqAttrSet);
            } catch (Exception e) {
            } finally {
                Boolean rv = Boolean.valueOf(pf != null);
                Application.invokeLater(new ExitLoopRunnable(this, rv));
            }
        }
    }

    private boolean showPageDialogFromNestedLoop(Window owner) {

        PageDialogRunnable dr = new PageDialogRunnable();
        Thread prtThread = new Thread(dr, "FX Page Setup Dialog Thread");
        prtThread.start();
        // the nested event loop will return after the runnable exits.
        Object rv = Toolkit.getToolkit().enterNestedEventLoop(dr);
        boolean rvbool = false;
        try {
            rvbool = ((Boolean)rv).booleanValue();
        } catch (Exception e) {
        }
        return rvbool;
    }

    /*
     * The update-Foo methods here are only used to update the
     * FX JobSettings as a result of changes by user interaction
     * with a print dialog. The new values are stored in the
     * PrintRequestAttributeSet and pulled from there in to the
     * equivalent FX public API JobSettings.
     */
    private void updateJobName() {
        String name = pJob2D.getJobName();
        if (!name.equals(settings.getJobName())) {
            settings.setJobName(name);
        }
    }

    private void updateOutputFile() {
        Destination dest =
            (Destination)printReqAttrSet.get(Destination.class);
        if (dest != null) {
            settings.setOutputFile(dest.getURI().getPath());
        } else {
            settings.setOutputFile("");
        }
    }

    private void updateCopies() {
        int nCopies = pJob2D.getCopies();
        if (settings.getCopies() != nCopies) {
            settings.setCopies(nCopies);
        }
    }

    private void updatePageRanges() {
        PageRanges ranges = (PageRanges)printReqAttrSet.get(PageRanges.class);
        // JDK sets default to 1,Integer.MAX_VALUE
        // So in this case I think we can just check for non-null and
        // only set if its non-null.
        if (ranges != null) {
            int[][] members = ranges.getMembers();
            if (members.length == 1) {
                PageRange range = new PageRange(members[0][0], members[0][1]);
                settings.setPageRanges(range);
            } else if (members.length > 0) {
                try {
                    ArrayList prList = new ArrayList<>();
                    int last = 0;
                    for (int i=0; i printerSet = Printer.getAllPrinters();
        for (Printer p : printerSet) {
            J2DPrinter p2d = (J2DPrinter)PrintHelper.getPrinterImpl(p);
            PrintService s = p2d.getService();
            if (s.equals(service)) {
                return p;
            }
        }
        return fxPrinter; // current printer.
    }

    @Override
    public void setPrinterImpl(PrinterImpl impl) {
        j2dPrinter = (J2DPrinter)impl;
        fxPrinter = j2dPrinter.getPrinter();
        try {
            pJob2D.setPrintService(j2dPrinter.getService());
        } catch (PrinterException pe) {
        }
    }

    @Override
    public PrinterImpl getPrinterImpl() {
        return j2dPrinter;
    }

    private J2DPrinter getJ2DPrinter(Printer printer) {
        return (J2DPrinter)PrintHelper.getPrinterImpl(printer);
    }

    public Printer getPrinter() {
        return fxPrinter;
    }

    public void setPrinter(Printer printer) {
        fxPrinter = printer;
        j2dPrinter = getJ2DPrinter(printer);
        try {
            pJob2D.setPrintService(j2dPrinter.getService());
        } catch (PrinterException pe) {
        }
    }

    private void updatePrinter() {
        PrintService currService = j2dPrinter.getService();
        PrintService jobService = pJob2D.getPrintService();
        if (currService.equals(jobService)) {
            return; // no change
        }
        Printer newFXPrinter = getFXPrinterForService(jobService);
        // The public setPrinter call also updates the job to be valid for
        // the new printer. Any old values not supported will be updated
        // to supported values. If we do that, then apply the new user
        // settings, any listener will see both sets of changes.
        // Its best to just see the single transition.
        fxPrinterJob.setPrinter(newFXPrinter);
    }

    private void updateSettingsFromDialog() {
        updatePrinter();
        updateJobName();
        updateOutputFile();
        updateCopies();
        updatePageRanges();
        updateSides();
        updateCollation();
        updatePageLayout();
        updatePaperSource();
        updateColor();
        updatePrintQuality();
        updatePrintResolution();
    }

    private void syncSettingsToAttributes() {
        syncJobName();
        syncOutputFile();
        syncCopies();
        syncPageRanges();
        syncSides();
        syncCollation();
        syncPageLayout();
        syncPaperSource();
        syncColor();
        syncPrintQuality();
        syncPrintResolution();
    }

    private void syncJobName() {
        pJob2D.setJobName(settings.getJobName());
    }

    private void syncOutputFile() {
        printReqAttrSet.remove(Destination.class);
        String file = settings.getOutputFile();
        if (file != null && !file.isEmpty()) {
             // check SE, check access ?
             URI uri = (new File(file)).toURI();
             Destination d = new Destination(uri);
             printReqAttrSet.add(d);
        }
    }

    private void syncCopies() {
        pJob2D.setCopies(settings.getCopies());
        printReqAttrSet.add(new Copies(settings.getCopies()));
    }

    private void syncPageRanges() {
        printReqAttrSet.remove(PageRanges.class);
        PageRange[] prArr = settings.getPageRanges();
        if (prArr != null && prArr.length>0) {
            int len = prArr.length;
            int[][] ranges = new int[len][2];
            for (int i=0;i currPageIndex) {
                nextPage = waitForNextPage(pageIndex);
            }
            return nextPage;
        }

        @Override
        public int print(Graphics g, PageFormat pf, int pageIndex) {
            if (jobError || jobCanceled || jobDone && !getPage(pageIndex)) {
                return Printable.NO_SUCH_PAGE;
            }
            int x = (int)pf.getImageableX();
            int y = (int)pf.getImageableY();
            int w = (int)pf.getImageableWidth();
            int h = (int)pf.getImageableHeight();
            Node appNode = currPageInfo.getNode();
            g.translate(x, y);
            printNode(appNode, g, w, h);
            return Printable.PAGE_EXISTS;
        }

        private void printNode(Node node, Graphics g, int w, int h) {
            PrismPrintGraphics ppg =
                    new PrismPrintGraphics((Graphics2D) g, w, h);
            NGNode pgNode = NodeHelper.getPeer(node);
            boolean errored = false;
            try {
                pgNode.render(ppg);
            } catch (Throwable t) {
                if (com.sun.prism.impl.PrismSettings.debug) {
                    System.err.println("printNode caught exception.");
                    t.printStackTrace();
                }
                errored = true;
            }
            ppg.getResourceFactory()
                    .getTextureResourcePool()
                    .freeDisposalRequestedAndCheckResources(errored);
        }

        @Override
        public Printable getPrintable(int pageIndex) {
            getPage(pageIndex);
            return this;
        }

        @Override
        public PageFormat getPageFormat(int pageIndex) {
            getPage(pageIndex);
            return currPageFormat;
        }

        /*
         * Since we return unknown number of pages, then
         * the behaviour must be that we can only signal
         * end of the job by returning NO_SUCH_PAGE from
         * the print(..) method.
         */
        @Override
        public int getNumberOfPages() {
            return Pageable.UNKNOWN_NUMBER_OF_PAGES;
        }

        /*
         * Executed on the application's thread.
         * Messages over to the printing thread.
         */
        private void implPrintPage(PageLayout pageLayout, Node node) {

            /* The public API printPage() is synchronized, so we know
             * that the app can't call it from 2 threads at the same
             * time, not that this is encouraged either.
             * Therefore when we are in this code, we know that any
             * previous page rendering has completed.
             * We also know that this means the app can't have 'queued up'
             * pages.
             * So, when we are in here, we know that the app is providing
             * the info for the next page.
             */
            pageDone = false;
            synchronized (monitor) {
                newPageInfo = new PageInfo(pageLayout, node);
                monitor.notify();
            }
            if (Toolkit.getToolkit().isFxUserThread()) {
                elo = new Object();
                Toolkit.getToolkit().enterNestedEventLoop(elo);
                elo = null;
            } else {
                while (!pageDone && !jobDone && !jobCanceled && !jobError) {
                    synchronized (monitor) {
                        try {
                            if (!pageDone) {
                                monitor.wait(1000);
                            }
                        } catch (InterruptedException e) {
                        }
                    }
                }
            }
        }

    } /* END J2DPageable class */


    @Override
    public boolean endJob() {
        if (jobRunning && !jobDone && !jobCanceled && !jobError) {
            jobDone = true;
            try {
                synchronized (monitor) {
                    monitor.notify();
                    return jobDone;
                }
            } catch (IllegalStateException e) {
                if (com.sun.prism.impl.PrismSettings.debug) {
                    System.err.println("Internal Error " + e);
                }
            }
        } else {
            return jobDone && !jobError;
        }
        return jobDone;
    }

    @Override
    public void cancelJob() {
        if (!pJob2D.isCancelled()) {
            pJob2D.cancel();
        }
        jobCanceled = true;
        if (jobRunning) {
            jobRunning = false;
            try {
                synchronized (monitor) {
                    monitor.notify();
                }
            } catch (IllegalStateException e) {
                if (com.sun.prism.impl.PrismSettings.debug) {
                    System.err.println("Internal Error " + e);
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy