使用 javax.print 库使用属性(托盘控件、双面打印等)进行打印

2022-09-01 17:46:37

一段时间以来,我一直在尝试确定一种使用标准Java Print库将具有某些属性的文件(特别是PDF文档)打印到某些托盘或使用双面打印的方法。

有很多关于如何做到这一点的文档,事实上,我已经研究并尝试了这些方法。典型的方式是这样的:

public static void main (String [] args) {
    try {

        PrintService[] pservices = PrintServiceLookup.lookupPrintServices(null, null);

        //Acquire Printer
        PrintService printer = null;
        for (PrintService serv: pservices) {
            System.out.println(serv.toString());
            if (serv.getName().equals("PRINTER_NAME_BLAH")) {
                printer = serv;
            }
        }

        if (printer != null) {
            System.out.println("Found!");


            //Open File
            FileInputStream fis = new FileInputStream("FILENAME_BLAH_BLAH.pdf");

            //Create Doc out of file, autosense filetype
            Doc pdfDoc = new SimpleDoc(fis, DocFlavor.INPUT_STREAM.AUTOSENSE, null);

            //Create job for printer
            DocPrintJob printJob = printer.createPrintJob();

            //Create AttributeSet
            PrintRequestAttributeSet pset = new HashPrintRequestAttributeSet();

            //Add MediaTray to AttributeSet
            pset.add(MediaTray.TOP);

            //Add Duplex Option to AttributeSet
            pset.add(Sides.DUPLEX);

            //Print using Doc and Attributes
            printJob.print(pdfDoc, pset);

            //Close File
            fis.close();

        }

    }
    catch (Throwable t) {
        t.printStackTrace();
    }
}

简而言之,您执行以下操作

  1. 查找打印机
  2. 创建打印机作业
  3. 创建属性集
  4. 将属性添加到属性集,如托盘和双面打印
  5. 使用属性集在打印机作业上调用打印

这里的问题是,尽管这是记录的方法,以及我从几个教程中发现的方法,但这种方法...不起作用。现在请记住,我知道这听起来不是很描述,但请听我说。我不会轻易这么说...

PrinterJob 的官方文档实际上提到,在默认实现中忽略 AttributeSet。这里看到的源代码表明这是真的 - 属性被传递并完全忽略。

所以显然,你需要某种扩展版本的类,这可能是基于特定的打印机及其功能?我试图编写一些测试代码来告诉我这些功能 - 我们在办公室设置了各种各样的打印机,无论大小,简单或充满花里胡哨 - 更不用说我的计算机上的几个驱动程序仅用于伪打印机驱动程序,这些驱动程序只是创建文档和模拟打印机而无需使用任何类型的硬件。测试代码如下:

public static void main (String [] args) {

    PrintService[] pservices = PrintServiceLookup.lookupPrintServices(null, null);

    for (PrintService serv: pservices) {
        System.out.println(serv.toString());

        printFunctionality(serv, "Trays", MediaTray.class);
        printFunctionality(serv, "Copies", Copies.class);
        printFunctionality(serv, "Print Quality", PrintQuality.class);
        printFunctionality(serv, "Color", ColorSupported.class);
        printFunctionality(serv, "Media Size", MediaSize.class);
        printFunctionality(serv, "Accepting Jobs", PrinterIsAcceptingJobs.class);
    }
}

private static void printFunctionality(PrintService serv, String attrName, Class<? extends Attribute> attr) {
    boolean isSupported = serv.isAttributeCategorySupported(attr);
    System.out.println("    " + attrName + ": " + (isSupported ? "Y" : "N"));
}

我发现的结果是,每台打印机都无一例外地返回“副本”受支持,而所有其他属性都不受支持。此外,每台打印机的功能都是相同的,无论这看起来多么难以置信。

不可避免的问题是多层次的:如何以注册属性的方式发送属性?此外,如何正确检测打印机的功能?实际上,PrinterJob 类是否实际上以可用的方式进行了扩展,或者属性是否始终被忽略?

我在互联网上找到的例子似乎向我表明,后一个问题的答案是“不,他们总是被忽略”,这对我来说似乎很荒谬(但随着我筛选数百页,这越来越可信)。这个代码是Sun简单地设置的,但从未工作到完成状态吗?如果是这样,是否有任何替代方案?


答案 1

问题在于,Java 打印 API 是世界之间的桥梁。打印机制造商不发布 JVM 的驱动程序。他们发布了Windows,Macintosh的驱动程序,也许有人有一个适用于一个或多个*nix平台的给定打印机的驱动程序。

随之而来的是一些在某个主机系统上的JVM内运行的Java代码。当您开始查询打印机功能时,您不是在与打印机交谈 - 您正在与java.awt.print中的桥接类交谈,该桥接类连接到JVM,该JVM与主机操作系统挂钩,该操作系统挂钩到为给定打印机安装的任何特定驱动程序。所以有几个地方可以分崩离析...您所在的特定 JVM 可能会也可能不会完全实现用于查询打印机功能的 API,更不用说为给定作业传递这些参数了。

一些建议:

  1. 看看javax.print类作为java.awt.print的替代品 - 我从那里打印得更幸运。
  2. 尝试为打印机使用其他打印驱动程序 -- 您可以定义与给定打印机的多个命名连接,每个连接具有不同的驱动程序。如果你有制造商提供的驱动程序,请尝试更通用的驱动程序,如果你有通用驱动程序,请尝试安装更具体的驱动程序。
  3. 在平台的备用 JVM 实现下运行代码

答案 2

因此,我们不可避免地找到了一种打印到不同托盘和不同设置的方法,但不是直接打印。我们发现无法通过printJob.print方法发送属性,而且很多东西都没有改变。但是,我们能够设置打印作业的名称,然后使用低级Perl脚本截获打印作业,解析名称,并在那里设置托盘和双面打印设置。这是一个极端的黑客,但它有效。Java打印机属性仍然不起作用,如果要设置它们,则需要找到另一种方法


推荐