Skip to content

gmuth/ipp-client-kotlin

Repository files navigation

ipp-client v3.1

A client implementation of the ipp protocol for java and kotlin. RFCs 8010, 8011, 3995 and 3996

License: MIT Build Quality Gate Status Sonar Coverage Maven Central

Usage

You may use ippfind or other ZeroConf tools for printer discovery. The CupsClient supports printer lookup by queue name. Repository ipp-samples contains examples how to use jmDNS.

implementation("de.gmuth:ipp-client:3.1")

README.md for version 2.x is still available.

// Initialize printer connection and log printer attributes
val ippPrinter = IppPrinter(URI.create("ipp:https://colorjet.local/ipp/printer"))
ippPrinter.log(logger)

// Marker levels
ippPrinter.markers.forEach { println(it) }
println("black: ${ippPrinter.marker(BLACK).levelPercent()} %")

// Print file
val file = File("A4-ten-pages.pdf")
val job = ippPrinter.printJob(
    file,
    copies(2),
    numberUp(2),
    jobPriority(30),
    jobName(file.name),
    DocumentFormat.PDF,
    pageRanges(2..3, 8..10),
    finishings(Punch, Staple),
    printerResolution(300, DPI),
    Sides.TwoSidedLongEdge,
    ColorMode.Monochrome,
    PrintQuality.High,
    Media.ISO_A4,
    mediaColWithSource("tray-1"),
    notifyEvents = listOf("job-state-changed", "job-stopped", "job-completed") // CUPS
)
job.subscription?.pollAndHandleNotifications { println(it) }

// Print remote file, make printer pull document from remote server
val remoteFile = URI.create("http:https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")
ippPrinter.printUri(remoteFile)

// Create job and send document
val job = ippPrinter.createJob(jobName(file.name))
job.sendDocument(FileInputStream(file))
job.waitForTermination()

// Manage jobs
ippPrinter.getJobs().forEach { println(it) }
ippPrinter.getJobs(WhichJobs.Completed)

val job = ippPrinter.getJob(4)
job.hold()
job.release()
job.cancel()
job.cupsGetDocuments() // CUPS only

// Print operator
ippPrinter.pause()
ippPrinter.resume()
ippPrinter.sound() // identify printer

// Subscribe and handle/log events (e.g. from CUPS) for 5 minutes
ippPrinter
    .createPrinterSubscription(notifyLeaseDuration = Duration.ofMinutes(5))
    .pollAndHandleNotifications() { event -> ... }

// Find supported media by size
ippPrinter
    .getMediaColDatabase() // PWG 5100.7 Job Extensions v2.0
    .findMediaBySize(ISO_A4)

// Media size supported? ready? which source?
ippPrinter.isMediaSizeSupported(ISO_A3)
ippPrinter.isMediaSizeReady(ISO_A4)
ippPrinter.sourcesOfMediaSizeReady(ISO_A4) // e.g. [tray-1, auto]

IppPrinter checks, if attribute values are supported by looking into '...-supported' printer attributes.

DocumentFormat("application/pcl")

WARN: according to printer attributes value 'application/pcl' is not supported.
document-format-supported (1setOf mimeMediaType) = application/pdf,application/postscript

To get to know a new printer and its supported features, you can run the inspection workflow of IppInspector. IPP traffic is saved to directory inspected-printers. The workflow will try to print a PDF.

// need an IPP server? https://openprinting.github.io/cups/doc/man-ippeveprinter.html
IppInspector.inspect(URI.create("ipp:https://ippeveprinter:8501/ipp/print"))

Exchange IppRequest for IppResponse

val uri = URI.create("ipp:https://colorjet.local/ipp/printer")
val file = File("A4-blank.pdf")

val ippClient = IppClient()
val request = IppRequest(IppOperation.PrintJob, uri).apply {
    // Constructor adds 'attributes-charset', 'attributes-natural-language' and 'printer-uri'
    operationGroup.attribute("document-format", IppTag.MimeMediaType, "application/pdf")
    documentInputStream = FileInputStream(file)
}
val response = ippClient.exchange(request)
println(response.jobGroup["job-id"])

Use the CupsClient to connect to a CUPS server. If you want to access a cups queue you can construct an IppPrinter from its uri.

// Connect to default ipp:https://localhost:631
val cupsClient = CupsClient()

// Credentials (e.g. for remote connections)
cupsClient.basicAuth("admin", "secret")

// List all queues
cupsClient.getPrinters().forEach {
    println("${it.name} -> ${it.printerUri}")
}

// List all completed jobs for queue
cupsClient.getPrinter("ColorJet_HP")
    .getJobs(WhichJobs.Completed)
    .forEach { println(it) }

// Default printer
val defaultPrinter = cupsClient.getDefault()

// Check capability
if (defaultPrinter.hasCapability(Capability.CanPrintInColor)) {
    println("${defaultPrinter.name} can print in color")
}

// Get canceled jobs and save documents
cupsClient.getJobsAndSaveDocuments(WhichJobs.Canceled)

// Setup IPP Everywhere Printer
cupsClient.setupIppEverywherePrinter(
    "myprinter",
    URI.create("ipp:https://myprinter.local:631/ipp/print"),
    "My description",
    "My location"
)

Print jpeg to 2" label printer

val printer = IppPrinter(URI.create("ipp:https://192.168.2.64"))
val jpegFile = File("label.jpeg")
val image = javax.imageio.ImageIO.read(jpegFile)
val width = 2540 * 2 // hundreds of mm

printer.printJob(
    jpegFile,
    DocumentFormat.JPEG,
    MediaCollection(
        MediaSize(width, width * image.height / image.width),
        MediaMargin(300) // 3 mm
    )
)

IANA Registrations

Section 2 and 6 registrations are available as Maps and can be queried:

// List media attributes and show syntax
IppRegistrationsSection2.attributesMap.values
    .filter { it.name.contains("media") }
    .sortedBy { it.collection }
    .forEach { println(it) }

// Lookup tag for attribute job-name
IppRegistrationsSection2.tagForAttribute("job-name") // IppTag.NameWithoutLanguage

It's not recommended to use IppRegistrations on the happy path of your control flow. You should rather e.g. lookup the correct tag during development and then use it in your code. Only when the library detects IPP issues through response codes, it consults the IppRegistrations to help identifying the issue.

This library implements a different concept then jipp. jipp seems very strict about IPP syntax and is not designed to cope with illegal IPP responses. My IPP library in contrast is designed for resilience. E.g. it accepts messages and attributes that use wrong IPP tags. I've not yet seen any IPP server implementation without a single encoding bug. IppInputStream for example includes workarounds for illegal responses of my HP and Xerox printers. From my experience this approach works better in real life projects than blaming the manufacturers firmware.

Logging

From version 3.0 onwards the library uses Java Logging - configure as you like. Tests can use Logging.configure() to load logging.properties from test/resources. The behaviour of my previously used ConsoleLogger is now implemented by StdoutHandler and SimpleClassNameFormatter. I moved all of my custom logging code to its own repository logging-kotlin.

Sources

To build the jar make sure you have JDK 11 installed. The default tasks build the jar in build/libs.

./gradlew

To install the artifact to your local maven repository run

./gradlew publishToMavenLocal

The build produces the jar, sources and javadoc artifacts. This software has no dependencies to javax.print, CUPS or ipptool. Operation has mostly been tested for target jvm. Android is supported since v1.6.

Package de.gmuth.ipp.core contains the usual encoding and decoding operations. RFC 8010 is fully supported. Package de.gmuth.ipp.client contains the IppClient and implementations of higher level IPP objects like IppPrinter, IppJob, IppSubscription and IppEventNotification.

IPP is based on the exchange of binary messages via HTTP. For reading and writing binary data DataInputStream and DataOutputStream are used. For message transportation IppClient uses HttpURLConnection.

Only Java runtimes (including Android) provide implementations of these classes. The Java standard libraries also provide support for SSL/TLS .