Cover V04, I04
Article
Listing 1
Listing 2
Listing 3
Sidebar 1

jul95.tar


Creating Filters for BSD Print Spoolers

Donald Brown

[Note: This article resulted from a project sponsored by the U.S. Department of Energy. The U.S. Government retains a nonexclusive, royalty-free license in and to the copyright covering this paper.]

Output devices are an ongoing source of frustration in most computing environments. It seems that if anything is going to fail, it's the printer or plotter, and most often they fail at the moment of greatest need. Given the seeming fragility of printer setups, the tendency is to enable only the basic capabilities. On UNIX systems, this seems especially so -- once the printer is set up and working, there's a great reluctance to make any changes for fear of breaking the successful configuration. With a simple understanding of filters, how they work, and how to create them, you can do a lot to extend the capabilities of the printers attached to your computer systems.

Before you begin, take time to understand your printer's or plotter's capabilities and how to invoke them. The manuals that come with the printer or plotter describe these capabilities. Don't lose those manuals.

What Is a Filter?

A UNIX filter is a program that reads a print stream as its input, called stdin, modifies the data in some way, then writes the modified data on its output, called stdout.

How Are Filters Used?

UNIX's shells allow a series of commands to be chained together, the output of one command being piped into the next. You can use filters in pipelined commands, as follows:

cat file | filter | lpr

In this case, the filter modifies the output of the cat program in some way, then sends the modified data on to the lpr program, which prints it. Filters can play an important role in preparing data for output.

Using Shell Scripts for Filters

The easiest type of filter to write is a shell script, so long as the filter doesn't have to do anything complicated. Shell scripts don't require any special compiling, and writing them is usually straightforward.

Listing 1 shows a shell script used as a filter. This filter places a special printer initialization string in the output sent to the printer. In this example, the initialization string is a simple reset command. This reflects a lesson learned through experience: if a printer is left in some unknown state by a previous print job, the current job gets printed using the format of the previous print job. Sending a reset string as the first command to the printer puts the printer back into a state for normal printing.

Note that the reset string includes the odd sequence '^['. This represents a single escape character embedded in the file. You can generate this in vi by typing a Control-V, then pressing the Escape key.

Once the reset string is sent, all remaining input is sent directly to the output with the following command:

cat 2>&1

which just says "take my error input and data input and write it on my output." After this, the script goes to sleep for 10 seconds to give the printer some time to absorb all this data, then exits.

Writing a Filter in C

I prefer writing filters in C for added flexibility. Listing 2 represents a filter similar to Listing 1, with a few more initialization commands.

The program in Listing 2 sends a reset string, followed by an initialization string. This particular initialization string takes better advantage of the inherent capabilities of the printer. It could just as well have been sent in a shell script. Note the "\033" in the initialization string. This is how the escape character is represented in a C character string. After sending the initialization string, the while() loop reads the input one character at a time and copies each character to the output.

After finding the end of the input file, this filter sends a formfeed (\014), then executes a command to flush its output buffer before exiting. Some printers simply hold the last page in their buffer until you take them offline and press the formfeed button. Sending a formfeed at the end of the print job is much more polite when you are sharing the printer with other people, as is often the case in UNIX.

Filters can also do more complex tasks. Listing 3 shows a filter written to process HP/GL files for the particular plotter we use. This filter sets the line style to the thinnest width. To do this, the filter reads the file looking for the HP/GL initialization command, IN. While searching for this command, the filter also sends the data read so far to the output. When it finds the IN command, the filter adds a PW0 command to the output data, to select a zero width pen. Once a zero-width pen is selected, the filter simply copies the rest of the input to the output.

This is just one example of what a filter written in C can do. Another filter I wrote takes a PostScript file and adds a command that tells the printer to use the paper in the 11x17 paper tray. I believe that Sun's NewSPrint product had a filter that detected the type of print job submitted, then resubmitted the job to another filter appropriate to that job's type. Such a filter is much too complicated for this article, but it suggests the kind of power one can put in a filter.

Pulling Everything Together with printcap

Print filters become more convenient when they are part of the printcap printer definitions. printcap's definitions tell the spooling system how to access the printer and, in some ways, how to format the job for the printer (for more information do a man printcap).

A sample printcap entry looks like this:

hp|laserjet:\
:lp=/dev/tty0:br#9600:\
:ms=pass8,opost,ixon,onlcr,crtscts:\
:sd=/var/spool/lpd:\
:lf=/var/adm/lpd-errs:sh:mx#0:\
:if=/usr/local/lib/filters/sethp:

The if= entry names the input filter file. This entry tells the spooling system that every job that hasn't specified another filter must filter through this program. In this example, any print job that hasn't selected another filter runs through the sethp program shown in Listing 2.

This particular printcap entry doesn't show alternate filters, but one could specify several alternates. Certain letters have been set aside for lpr to invoke other filters. These letters (c, d, g, n, r, t, v) each have a meaning assigned to them, but for the purposes of this article, I will ignore these meanings. A filter invoked by the c option was originally intended to handle cifplot-formatted files, but if you never print that type of file, you can use this letter to invoke your special print filter. You might use -c to invoke landscape mode, or -d to set a special font and line spacing. Be sure to let the users know which filter options do what.

To add a c-filter and a d-filter to the printcap entry, write:

:cf=/usr/local/lib/filters/setland:\
:df=/usr/local/lib/filters/smallfont:

Notice that each line of a printcap entry must end with a backslash ("\") except for the last line, and each line must be indented except for the first line. Also, you must separate each option in the printcap entry by a colon (":"), including the last line. It doesn't hurt to have extra colons, so each line but the first usually has a colon at the beginning and the end.

To print a job in landscape mode they invoke the landscape filter with the following command:

lpr -c filename

Instead of using filter options, you can make multiple copies of the printcap entry, renaming each one and change the if filter for each to perform a different task. For instance, if you create a printcap entry for a printer called landscape and assign it setland as its input filter, users invoke it with the following command:

lpr -Plandscape filename

Multiple printcap entries create multiple logical printers. Users just name the printer they want to send the job to. lpr's -P option names the printer.

Caveats for Remote Printers

printcap entries that look like this

:lp=:rm=somehost:rp=lp:

specify a remote printer. printcap filter options are not passed on to remote printers. If the printcap entry specifies a remote machine and printer name, any filters specified in the printcap entry will not be invoked on the local machine or the remote machine.

You can still invoke a filter on your print job, however. Just filter the job before sending it to the remote printer. Pipelined commands like those at the beginning of this article show how to do this. Taking it a step further, you can set up a C-shell alias to handle this in one step as for example:

alias wpr 'cat!* | filter | lpr'

Users send their print jobs and invoke the right filter by simply typing:

wpr filename

Conclusion

Creating print filters make it easier to format print jobs that take advantage of a printer's or plotter's capabilities. Including the filter in the printcap definition simplifies user access on the command line by allowing for either an option or a logical printer name. Invoking special functions, such as landscape mode, requires only that the user remember how to invoke the filter. Aliasing a pipelined command simplifies uglier, hard-to-type UNIX shell syntax.

About the Author

Donald Brown graduated in 1981 with a BS degree in Design and Graphics Technology from BYU. He worked for several years in Product Production Engineering at Hughes Aircraft Company in El Segundo, CA, where his responsibilities were to take the lead in integrating CAD into the department. He is currently working for Boeing Computer Services, Richland, as a Software Engineer. He has written several programs in C on DOS and UNIX. His current duties include UNIX System Administration, mainly on SunOS and Solaris systems.