Print to specific printer (IPP URI) in Java
Asked Answered
C

4

16

Is there any way in Java to print to a specific IPP printer? All of the sample code and tutorials I've found focus on how to print a particular type of document, using something like the following:

DocFlavor flavor = DocFlavor.INPUT_STREAM.POSTSCRIPT;
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
aset.add(MediaSizeName.ISO_A4);
PrintService[] pservices =
             PrintServiceLookup.lookupPrintServices(flavor, aset);
if (pservices.length > 0) {
    DocPrintJob pj = pservices[0].createPrintJob();
    try {
        FileInputStream fis = new FileInputStream("test.ps");
        Doc doc = new SimpleDoc(fis, flavor, null);
        pj.print(doc, aset);
    } catch (FileNotFoundException fe) {
    } catch (PrintException e) { 
    }
}

This snippet simply prints to the first printer found that is capable of printing the document. In my case, I want to lookup a printer by its URI, but PrintServiceLookup doesn't seem to support this. I've tried using a PrintServiceAttributeSet, instead of PrintRequestAttributeSet, and adding a PrinterURI attribute, but that doesn't return any printers. I suspect the lookup service is looking for a printer that can change its destination URI, rather than looking for the printer with that URI.

As a last resort, I thought about just enumerating through all of the PrintServices returned by lookupPrintServices, but the URI is not in any of the attributes. The printer name is there, but I need the URI.

For background, my webapp needs to print a barcode to a specific printer, based on the current user. Each user is associated with a printer URI, which points to a printer on a CUPS server. The printer URI is the only information I have, and I can't constrain the printer name to match the URI or a substring of the URI.

Edit: To clarify a bit, I don't need to render the data, I just need to copy a blob to a given printer. The part I can't figure out is how to identify a printer by its IPP URI.

Clarey answered 31/10, 2009 at 19:11 Comment(0)
C
19

I finally found a way to do this, by using jipsi:

URI printerURI = new URI("ipp://SERVER:631/printers/PRINTER_NAME");
IppPrintService svc = new IppPrintService(printerURI);
InputStream stream = new BufferedInputStream(new FileInputStream("image.epl"));
DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE;
Doc myDoc = new SimpleDoc(stream, flavor, null);
DocPrintJob job = svc.createPrintJob();
job.print(myDoc, null);

I have to admit I'm disappointed at having to use a 3rd-party library to do something so seemingly simple as printing to a specific printer.

UPDATE

DR points out in the comments that jipsi has a new home, and a new name.

Cups4J is a nice alternative, but as the name implies it may not work correctly if the destination is not a CUPS server. I have had good results using Cups4J to print directly to a Zebra thermal printer.

Clarey answered 2/11, 2009 at 18:24 Comment(7)
The link seems to be dead, but it looks like the original code is available on code.google.com, although under a different name (jspi): code.google.com/p/jspi (Only svn checkout, no downloads)Counterplot
@DR Thanks for the link, I've updated the answer with the information.Clarey
How did you figure out the PRINTER_NAME to tack onto the end of the url?Menial
@Menial I already knew the printer name, it was what we configured in the CUPS server. I had the complete URI from CUPS, I just needed a way to print to it.Clarey
A newly refactored and Mavenized version of JSPI is now available over at github.com/bhagyas/jspiPebble
@Pebble it has died at now how I can see. Last commit 3 years agoWanhsien
@Wanhsien There are no updates, but feel free to make suggestions to make it better.Pebble
O
1

I do not think you can get a printer the way you would like to (I think the Java Print mechanism predates IPP).

You may, however, if I recall correctly be able to render your print job locally and then ship the bytes of the output stream to the target CUPS server "by hand". Would this be "good enough" for you?

Odontoblast answered 31/10, 2009 at 19:24 Comment(1)
Actually, the print job has already been rendered by an external process into EPL, which is native to the printer (similar to postscript or PCL). So yes, if I can ship the bytes to the CUPS server that would be good enough. I'd like to avoid implementing an IPP client by hand though if possible.Clarey
H
1

To only submit a printable document format like PDF via IPP to a printer (or to CUPS) this code provides a minimalistic implementation without dependencies. ipp-printjob-java has basic support for decoding the ipp response.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class IppPrintJob {

  public static void main(String args[]) throws Exception {
    URI printerURI = URI.create("http://colorjet:631/ipp/printer");
    File file = new File("A4-blank.pdf");
    short status = new IppPrintJob()
      .printDocument(printerURI, new FileInputStream(file));
    System.out.println(String.format("ipp status: %04X", status));
  }

  short printDocument(
    URI uri, InputStream documentInputStream
  ) throws IOException {
    HttpURLConnection httpURLConnection =
      (HttpURLConnection) uri.toURL().openConnection();
    httpURLConnection.setDoOutput(true);
    httpURLConnection.setRequestProperty("Content-Type", "application/ipp");
    OutputStream outputStream = httpURLConnection.getOutputStream();
    DataOutputStream dataOutputStream =
      new DataOutputStream(httpURLConnection.getOutputStream());
    dataOutputStream.writeShort(0x0101); // ipp version
    dataOutputStream.writeShort(0x0002); // print job operation
    dataOutputStream.writeInt(0x002A); // request id
    dataOutputStream.writeByte(0x01); // operation group tag
    writeAttribute(dataOutputStream, 0x47, "attributes-charset", "utf-8");
    writeAttribute(dataOutputStream, 0x48, "attributes-natural-language", "en");
    writeAttribute(dataOutputStream, 0x45, "printer-uri", uri.toString());
    dataOutputStream.writeByte(0x03); // end tag
    documentInputStream.transferTo(outputStream);
    dataOutputStream.close();
    outputStream.close();
    if (httpURLConnection.getResponseCode() == 200) {
      DataInputStream dataInputStream =
        new DataInputStream(httpURLConnection.getInputStream());
      System.out.println(String.format("ipp version %d.%s",
        dataInputStream.readByte(), dataInputStream.readByte()
      ));
      return dataInputStream.readShort();
    } else {
      throw new IOException(String.format("post to %s failed with http status %d",
        uri, httpURLConnection.getResponseCode()
      ));
    }
  }

  void writeAttribute(
    DataOutputStream dataOutputStream, int tag, String name, String value
  ) throws IOException
  {
    Charset charset = StandardCharsets.UTF_8;
    dataOutputStream.writeByte(tag);
    dataOutputStream.writeShort(name.length());
    dataOutputStream.write(name.getBytes(charset));
    dataOutputStream.writeShort(value.length());
    dataOutputStream.write(value.getBytes(charset));
  }

}
Heeled answered 19/3, 2020 at 14:20 Comment(0)
H
0

Using this ipp-client you can submit a PDF file to a printer (or CUPS) which supports this document format:

new IppPrinter(URI.create("ipp://myprinter")).printJob(
    File("mydocument.pdf"), documentFormat("application/pdf")
);
Heeled answered 21/6, 2022 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.