Writing a CUPS filter for Mac OS X Snow Leopard (Mac OS v10.6)

I wanted to be able to save a PDF version of anything I printed automatically - without having to manually generate the PDF as a separate action, or needing to send the documents to print twice - once to a physical printer, then to a virtual printer.

My solution was to dig into the CUPS (Common UNIX Printing System) components in Mac OS X. CUPS uses a series of filters (command line programs) to convert a document from its original content into a PostScript file for sending to the printer (assuming a PostScript printer that is).

By working with configuration files, it is possible to insert an additional filter step (my own program) into the standard filter chain - giving me access to the PostScript content before it is sent to the printer (from which a PDF can then be easily generated by calling existing command line programs).

The first stage is to add an entry to the filters pipeline.

If you inspect the contents of:

/usr/share/cups/mime/

There are 4 files:

apple.convs
apple.types
mime.convs
mime.types

It's worth reading the contents of these (especially the comments) - but we're not going to edit these files. Some things worth noting are:

From /usr/share/cups/mime/mime.convs

# DO NOT EDIT THIS FILE, AS IT IS OVERWRITTEN WHEN YOU INSTALL NEW
# VERSIONS OF CUPS. Instead, create a "local.convs" file that
# reflects your local configuration changes.
# Format of Lines:
#
# source/type destination/type cost filter
# All filters *must* accept the standard command-line arguments
# (job-id, user, title, copies, options, [filename or stdin]) to
# work with CUPS.

From /usr/share/cups/mime/apple.convs

# The "cost" field must be less than the value for the same entry
# in mime.convs so that these filters replaces the ones defined
# in that file.

So, based on this, we will create 2 new files in the same directory:

sudo touch /usr/share/cups/mime/local.convs
sudo touch /usr/share/cups/mime/local.types

The contents of /usr/share/cups/mime/local.convs should be:

application/postscript application/foo 1 bar
application/foo application/vnd.cups-postscript 1 pstops

The contents of /usr/share/cups/mime/local.types should be:

application/foo

We're introducing a new mime type locally and we're replicating the application/postscript to application/vnd.cups-postscript conversion with 2 steps, both of which have a lower 'cost'; the option we're 'replacing' is the following line from /usr/share/cups/mime/mime.convs

application/postscript application/vnd.cups-postscript 66 pstops

Note that we're using the same program to handle the conversion (pstops), but that our combined 'cost' is not a realistic one - it's deliberately lower so that our filter(s) will be picked; we're not altering any of the existing configuration files (there is no need to comment things out).

The second stage is to add the filter program itself.

Here is an example written in PHP (but intended to be run as a command line program):

#!/usr/bin/php
<?php

$arguments = array();
$arguments['job'] = $_SERVER['argv'][1]; // $_SERVER['argv'][0] is the program path
$arguments['user'] = $_SERVER['argv'][2];
$arguments['title'] = $_SERVER['argv'][3];
$arguments['copies'] = $_SERVER['argv'][4];
$arguments['options'] = $_SERVER['argv'][5];

if (count($_SERVER['argv']) == 7) {
$postscript = file_get_contents($_SERVER['argv'][6]);
} else {
$postscript = stream_get_contents(STDIN);
}

/*
Here you could:
- write the arguments to a file
- write the PostScript content to a file
- call the pstopdf program (passing the PostScript file path) to generate a PDF file from the PostScript file (it will be generated in the same directory that the passed PostScript file resides in)
- modify the PostScript before it is passed to the next filter (if you know what you're doing - watch out for errors!)
*/

echo $postscript; // for passing on to the next filter
exit;

The filter programs reside in:

/usr/libexec/cups/filter/

Following the example, this file would be saved as simply 'bar' (it does not have a .php extension). It's important to note that your program should have the same owner, group and permissions as the other filters in the directory; if you're using a symlink, both the symlink and the program it references must have these same owner, group and permissions as well. Otherwise you will receive errors when try to print ("The printer software was installed incorrectly. Please reinstall the printer's software or contact the manufacturer for assistance.") and the cups error_log (at /private/var/cups/error_log - use the Console app) will report something similar to:

Unable to execute /usr/libexec/cups/filter/bar: insecure file permissions (0100755)
Unable to start filter "bar" - Operation not permitted.
Stopping job because the scheduler could not execute a filter.

Tagged , , and .

NV21 / YUV to greyscale (or grayscale) conversion for Android camera preview

This was very helpful (and took a while to find): NV21 / YUV to greyscale (or grayscale) conversion for Android.

If you only need grayscale information, the first (width * height) bytes of the preview are provided as unsigned byte intensities.

If you call print on the byte array entries directly the values are incorrect (they are presumed to represent signed integers); you'll need to convert each byte value to an unsigned integer yourself:

int value = (int) _data[i] & 0xFF;

(this may not be the most optimal way, but it seems to work).

The specifics of the format are detailed at YUV pixel formats at FOURCC.org (you'll need to read the NV12 entry as well) but these didn't make sense until I had read the post in the first link.

Tagged , and .

Starting my Ph.D.

I'm in the second week of my Ph.D. at the School of Computer Science and Informatics at University College Dublin. I still seem to be running around like a headless chicken, filling in forms and trying to figure out how to use all the various systems, but it will be 4 years in total, so I think it's ok for me to take more than a few weeks to settle in.

Matt and I moved into a flat in Milltown, which is about 20 minutes walk from the main campus. Matt's fiancée Laura joined us at the weekend and our NTL broadband was installed yesterday.

Matt and I also heard yesterday that we have both been awarded full IRCSET scholarships.

Tagged , and .

Moving to Dublin

As some of you may already know, I'm moving to Dublin with Matt and Laura, to study a Ph.D. in Pervasive Computing at UCD.

My last day with Acknowledgement was August 28th and I left London 2 days later. I'm currently spending a few days with my parents in Hull, before flying over to Dublin to start my studies.

Tagged , and .

Off to Dublin

Currently sat in Gatwick Airport, waiting for my flight to start boarding; I'm off to Dublin to see about a Ph.D. at UCD. Yes, that is where Matt is going - but being cheeky, I asked him to pass my details on. I'm not sure that they were after a cling-on (+5 for Star Trek joke!) - but at least they've let me go over for a chat.

Tagged , and .

Most used blog post tags

Blog entries by month

  1. January 2012 (1)
  2. August 2011 (2)
  3. March 2011 (1)
  4. February 2011 (1)
  5. November 2010 (1)
  6. October 2010 (1)
  7. August 2010 (2)
  8. January 2009 (1)
  9. December 2008 (1)
  10. November 2008 (1)
  11. October 2008 (2)
  12. September 2008 (3)