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.
|