Although LPD handles network protocols, queuing, access control, and other aspects of printing, most of the real work happens in the filters. Filters are programs that communicate with the printer and handle its device dependencies and special requirements. In the simple printer setup, we installed a plain text filter---an extremely simple one that should work with most printers (section Installing the Text Filter ).
However, in order to take advantage of format conversion, printer accounting, specific printer quirks, and so on, you should understand how filters work. It will ultimately be the filter's responsibility to handle these aspects. And the bad news is that most of the time you have to provide filters yourself. The good news is that many are generally available; when they're not, they're usually easy to write.
Also, FreeBSD comes with one, /usr/libexec/lpr/lpf
,
that works with many printers that can print plain text.
(It handles backspacing and tabs in the file, and does
accounting, but that's about all it does.) There are also
several filters and filter components in the FreeBSD ports
collection.
Here's what you'll find in this section:
lpr -t
to print troff data, or
lpr -d
to print TeX DVI data, or lpr -v
to
print raster image data, and so forth. I recommend
reading this section.
lpf
, a fairly complete
if simple text filter for line printers (and laser
printers that act like line printers) that comes with
FreeBSD. If you need a quick way to get printer
accounting working for plain text, or if you have a
printer which emits smoke when it sees backspace
characters, you should definitely consider lpf
.
As mentioned before, a filter is an executable program started by LPD to handle the device-dependent part of communicating with the printer.
When LPD wants to print a file in a job, it starts a
filter program. It sets the filter's standard input to
the file to print, its standard output to the printer, and
its standard error to the error logging file (specified in
the lf
capability in /etc/printcap
, or
/dev/console
by default).
Which filter LPD starts and the filter's arguments depend
on what's listed in the /etc/printcap
file and
what arguments the user specified for the job on the
lpr
command line. For example, if the user typed
lpr -t
, LPD would start the troff filter, listed in
the tf
capability for the destination printer. If
the user wanted to print plain text, it would start the
if
filter (this is mostly true: see
Output Filters
for
details).
There are three kinds filters you can specify in
/etc/printcap
:
[-c] -wwidth -llength -iindent -n login -h host acct-file
where
-c
appears if the job's submitted with lpr -l
width
is the value from the pw
(page width)
capability specified in /etc/printcap
,
default 132
length
is the value from the pl
(page length)
capability, default 66
indent
is the amount of the indentation from lpr -i
,
default 0
login
is the account name of the user printing the file
host
is the host name from which the job was submitted
acct-file
is the name of the accounting file from the af
capability.
-xpixel-width -ypixel-height -n login -h host acct-file
where pixel-width is the value from the px
capability (default 0) and pixel-height is the
value from the py
capability (default 0).
-wwidth -llength
which are identical to the text filters -w
and
-l
arguments.
Filters should also exit with the following exit status:
If the filter printed the file successfully.
If the filter failed to print the file but wants LPD to try to print the file again. LPD will restart a filter if it exits with this status.
If the filter failed to print the file and doesn't want LPD to try again. LPD will throw out the file.
The text filter that comes with the FreeBSD release,
/usr/libexec/lpr/lpf
, takes advantage of the page
width and length arguments to determine when to send a
form feed and how to account for printer usage. It uses
the login, host, and accounting file arguments to make the
accounting entries.
If you're shopping for filters, see if they're LPD-compatible. If they are, they must support the argument lists described above. If you plan on writing filters for general use, then have them support the same argument lists and exit codes.
If you're the only user of your computer and PostScript (or other language-based) printer, and you promise to never send plain text to your printer and to never use features of various programs that will want to send plain text to your printer, then you don't need to worry about this section at all.
But, if you would like to send both PostScript and plain
text jobs to the printer, then you're urged to augment
your printer setup. To do so, we have the text filter
detect if the arriving job is plain text or PostScript.
All PostScript jobs must start with %!
(for
other printer languages, see your printer documentation).
If those are the first two characters in the job, we have
PostScript, and can pass the rest of the job directly. If
those aren't the first two characters in the file, then
the filter will convert the text into PostScript and print
the result.
How do we do this?
If you've got a serial printer, a great way to do it is to
install lprps
. lprps
is a PostScript printer
filter which performs two-way communication with the
printer. It updates the printer's status file with
verbose information from the printer, so users and
administrators can see exactly what the state of the
printer is (such as ``toner low'' or ``paper jam''). But
more importantly, it includes a program called psif
which detects whether the incoming job is plain text and
calls textps
(another program that comes with
lprps
) to convert it to PostScript. It then uses
lprps
to send the job to the printer.
lprps
should be part of the FreeBSD ports collection
(see
The Ports Collection
); if not,
it should be shortly. You can fetch, build and install it
yourself, of course. After installing lprps
, just
specify the pathname to the psif
program that's part
of lprps
. If you installed lprps
from the ports
collection, use the following in the serial PostScript
printer's entry in /etc/printcap
:
:if=/usr/local/libexec/psif:
You should also specify the rw
capability; that tells
LPD to open the printer in read-write mode.
If you have a parralel PostScript printer (and therefore
can't use two-way communication with the printer, which
lprps
needs), you can use the following shell script
as the text filter:
#!/bin/sh # # psif - Print PostScript or plain text on a PostScript printer # Script version; NOT the version that comes with lprps # Installed in /usr/local/libexec/psif # read first_line first_two_chars=`expr "$first_line" : '\(..\)'` if [ "$first_two_chars" = "%!" ]; then # # PostScript job, print it. # echo $first_line && cat && printf "\004" && exit 0 exit 2 else # # Plain text, convert it, then print it. # ( echo $first_line; cat ) | /usr/local/bin/textps && printf "\004" && exit 0 exit 2 fi
textps
is a program we installed
separately to convert plain text to PostScript. You can
use any text-to-PostScript program you wish. The FreeBSD
ports collection (see
The Ports Collection
) includes a full featured text-to-PostScript
program called a2ps
that you might want to
investigate.
PostScript is the de facto standard for high quality typesetting and printing. PostScript is, however, an expensive standard. Thankfully, Alladin Enterprises has a free PostScript workalike called Ghostscript that runs with FreeBSD. Ghostscript can read most PostScript files and can render their pages onto a variety of devices, including many brands of non-PostScript printers. By installing Ghostscript and using a special text filter for your printer, you can make your non-PostScript printer act like a real PostScript printer.
Ghostscript should be in the FreeBSD ports collection, if you'd like to install it from there. You can fetch, build, and install it quite easily yourself, as well.
To simulate PostScript, we have the text filter detect if it's printing a PostScript file. If it's not, then the filter will pass the file directly to the printer; otherwise, it will use Ghostscript to first convert the file into a format the printer will understand.
Here's an example: the following script is a text filter
for Hewlett Packard DeskJet 500 printers. For other
printers, substitute the -sDEVICE
argument to the
gs
(Ghostscript) command. (Type gs -h
to get a
list of devices the current installation of Ghostscript
supports.)
#!/bin/sh # # ifhp - Print Ghostscript-simulated PostScript on a DesJet 500 # Installed in /usr/local/libexec/hpif # # Treat LF as CR+LF: # printf "\033&k2G" || exit 2 # # Read first two characters of the file # read first_line first_two_chars=`expr "$first_line" : '\(..\)'` if [ "$first_two_chars" = "%!" ]; then # # It's PostScript; use Ghostscript to scan-convert and print it # /usr/local/bin/gs -dSAFER -dNOPAUSE -q -sDEVICE=djet500 -sOutputFile=- - \ && exit 0 else # # Plain text or HP/PCL, so just print it directly; print a form # at the end to eject the last page. # echo $first_line && cat && printf "\f" && exit 2 fi exit 2
if
capability:
:if=/usr/local/libexec/hpif:
That's it. You can type lpr plain.text
and lpr
whatever.ps
and both should print successfully.
After completing the simple setup described in Simple Printer Setup , the first thing you'll probably want to do is install conversion filters for your favorite file formats (besides plain ASCII text).
Conversion filters make printing various kinds of files easy. As an example, suppose we do a lot of work with the TeX typesetting system, and we have a PostScript printer. Every time we generate a DVI file from TeX, we can't print it directly until we convert the DVI file into PostScript. The command sequence goes like this:
dvips seaweed-analysis.dvi
lpr seaweed-analysis.ps
By installing a conversion filter for DVI files, we can
skip the hand conversion step each time by having LPD do
it for us. Now, each time we get a DVI file, we're just
one step away from printing it:
lpr -d seaweed-analysis.dvi
We got LPD to do the DVI file conversion for us by
specifying the -d
option. Section
Formatting and Conversion Options
lists the conversion options.
For each of the conversion options you want a printer to
support, install a conversion filter and specify
its pathname in /etc/printcap
. A conversion
filter is like the text filter for the simple printer
setup (see section
Installing the Text Filter
) except that instead
of printing plain text, the filter converts the file
into a format the printer can understand.
You should install the conversion filters you expect to use. If you print a lot of DVI data, then a DVI conversion filter is in order. If you've got plenty of troff to print out, then you probably want a troff filter.
The following table summarizes the filters that LPD
works with, their capability entries for the
/etc/printcap
file, and how to invoke them with
the lpr
command:
/etc/printcap File type Capability lpr option ------------ ------------- ---------- cifplot cf -c DVI df -d plot gf -g ditroff nf -n FORTRAN text rf -f troff tf -t raster vf -v plain text if none, -p, or -l
In our example, using lpr -d
means the printer
needs a df
capability in its entry in
/etc/printcap
.
Despite what others might contend, formats like FORTRAN
text and plot are probably obsolete. At your site, you
can give new meanings to these or any of the formatting
options just by installing custom filters. For example,
suppose you'd like to directly print Printerleaf files
(files from the Interleaf desktop publishing program),
but will never print plot files. You could install a
Printerleaf conversion filter under the gf
capability and then educate your users that lpr -g
mean ``print Printerleaf files.''
Since conversion filters are programs you install
outside of the base FreeBSD installation, they should
probably go under /usr/local
. The directory
/usr/local/libexec
is a popular location, since
they they're specialized programs that only LPD will
run; regular users shouldn't ever need to run them.
To enable a conversion filter, specify its pathname
under the appropriate capability for the destination
printer in /etc/printcap
.
In our example, we'll add the DVI conversion filter to
the entry for the printer named bamboo
. Here's the
example /etc/printcap
file again, with the new
df
capability for the printer bamboo
# # /etc/printcap for host rose - added df filter for bamboo # rattan|line|diablo|lp|Diablo 630 Line Printer:\ :sh:sd=/var/spool/lpd/rattan:\ :lp=/dev/lpt0:\ :if=/usr/local/libexec/if-simple: bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\ :sh:sd=/var/spool/lpd/bamboo:\ :lp=/dev/ttyd5:fs#0x82000e1:xs#0x820:rw:\ :if=/usr/local/libexec/psif:\ :df=/usr/local/libexec/psdf:
/usr/local/libexec/psdf
. Here's that script:
#!bin/sh # # DVI to PostScript printer filter # Installed in /usr/local/libexec/psdf # # Invoked by lpd when user runs lpr -d # exec /usr/local/bin/dvips -f | /usr/local/libexec/lprps "$@"
dvips
in filter mode (the -f
argument) on standard input, which is the job to print.
It then starts the PostScript printer filter lprps
(see section
Accomodating Plain Text Jobs on PostScript Printers
) with the arguments LPD passed to this script.
lprps
will use those arguments to account for the
pages printed.
Since there's no fixed set of steps to install conversion filters, let me instead provide more examples. Use these as guidance to making your own filters. Use them directly, if appropriate.
This example script is a raster (well, GIF file, actually) conversion filter for a Hewlett Packard LaserJet III-Si printer:
#!/bin/sh # # hpvf - Convert GIF files into HP/PCL, then print # Installed in /usr/local/libexec/hpvf PATH=/usr/X11R6/bin:$PATH; export PATH giftopnm | ppmtopgm | pgmtopbm | pbmtolj -resolution 300 \ && exit 0 \ || exit 2
Here's the /etc/printcap
file with an entry for
a printer using the above filter:
# # /etc/printcap for host orchid # teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\ :lp=/dev/lpt0:sh:sd=/var/spool/lpd/teak:mx#0:\ :if=/usr/local/libexec/hpif:\ :vf=/usr/local/libexec/hpvf:
The following script is a conversion filter for troff
data from the groff typesetting system for the
PostScript printer named bamboo
:
#!/bin/sh # # pstf - Convert groff's troff data into PS, then print. # Installed in /usr/local/libexec/pstf # exec grops | /usr/local/libexec/lprps "$@"
lprps
again to handle
the communication with the printer. If the printer were
on a parallel port, we'd use this script instead:
#!/bin/sh # # pstf - Convert groff's troff data into PS, then print. # Installed in /usr/local/libexec/pstf # exec grops
/etc/printcap
to enable the filter:
:tf=/usr/local/libexec/pstf:
Here's an example that might make old hands at FORTRAN
blush. It's a FORTRAN-text filter for any printer that
can directly print plain text. We'll install it for the
printer teak
:
#!/bin/sh # # hprf - FORTRAN text filter for LaserJet 3si: # Installed in /usr/local/libexec/hprf # printf "\033&k2G" && fpr && printf "\f" && exit 0 exit 2
/etc/printcap
for the printer teak
to enable this filter:
:rf=/usr/local/libexec/hprf:
Here's one final, somewhat complex example. We'll add a
DVI filter to the LaserJet printer teak
introduced
earlier. First, the easy part: updating
/etc/printcap
with the location of the DVI
filter:
:df=/usr/local/libexec/hpdf:
Now, for the hard part: making the filter. For that, we
need a DVI-to-LaserJet/PCL conversion program. The
FreeBSD ports collection (see
The Ports Collection
) has one: dvi2xx
is the name of
the package. Installing this package gives us the
program we need, dvilj2p
, which converts DVI into
LaserJet IIp, LaserJet III, and LaserJet 2000 compatible
codes.
dvilj2p
makes the filter hpdf
quite complex
since dvilj2p
can't read from standard input. It
wants to work with a filename. What's worse, the
filename has to end in .dvi
so using
/dev/fd/0
for standard input is problematic.
We can get around that problem by linking (symbolically)
a temporary file name (one that ends in .dvi
) to
/dev/fd/0
, thereby forcing dvilj2p
to read
from standard input.
The only other fly in the ointment is the fact that we
can't use /tmp for the temporary link. Symbolic links
are owned by user and group bin
. The filter runs
as user daemon
. And the /tmp
directory
has the sticky bit set. The filter can create the link,
but it won't be able clean up when done and remove it
since the link will belong to a different user.
Instead, the filter will make the symbolic link in the
current working directory, which is the spooling
directory (specified by the sd
capability in
/etc/printcap
). This is a perfect place for
filters to do their work, especially since there's
(sometimes) more free disk space in the spooling directory
than under /tmp
.
Here, finally, is the filter:
#!/bin/sh # # hpdf - Print DVI data on HP/PCL printer # Installed in /usr/local/libexec/hpdf PATH=/usr/local/bin:$PATH; export PATH # # Define a function to clean up our temporary files. These exist # in the current directory, which will be the spooling directory # for the printer. # cleanup() { rm -f hpdf$$.dvi } # # Define a function to handle fatal errors: print the given message # and exit 2. Exiting with 2 tells LPD to don't try to reprint the # job. # fatal() { echo "$@" 1>&2 cleanup exit 2 } # # If user removes the job, LPD will send SIGINT, so trap SIGINT # (and a few other signals) to clean up after ourselves. # trap cleanup 1 2 15 # # Make sure we're not colliding with any existing files. # cleanup # # Link the DVI input file to standard input (the file to print). # ln -s /dev/fd/0 hpdf$$.dvi || fatal "Cannot symlink /dev/fd/0" # # Make LF = CR+LF # printf "\033&k2G" || fatal "Cannot initialize printer" # # Convert and print. Return value from dvilj2p doesn't seem to be # reliable, so we ignore it. # dvilj2p -M1 -q -e- dfhp$$.dvi # # Clean up and exit # cleanup exit 0
All these conversion filters accomplish a lot for your
printing environment, but at the cost forcing the user
to specify (on the lpr
command line) which one to
use. If your users aren't particularly computer
literate, having to specify a filter option will become
annoying. What's worse, though, is that an incorrectly
specified filter option may run a filter on the wrong
type of file and cause your printer to spew out hundreds
of sheets of paper.
Rather than install conversion filters at all, you might
want to try having the text filter (since it's the
default filter) detect the type of file it's asked to
print and then automatically run the right conversion
filter. Tools such as file
can be of help here.
Of course, it'll be hard to determine the differences
between some file types---and, of course, you can
still provide conversion filters just for them.
The FreeBSD ports collection has a text filter that
performs automatic conversion called apsfilter
. It
can detect plain text, PostScript, and DVI files, run
the proper conversions, and print.
The LPD spooling system supports one other type of filter that we've not yet explored: an output filter. An output filter is intended for printing plain text only, like the text filter, but with many simplifications. If you're using an output filter but no text filter, then
-wwidth -llength
where width is from the pw
capability and
length is from the pl
capability for the
printer in question.
Don't be seduced by an output filter's simplicity. If you'd like each file in a job to start on a different page an output filter won't work. Use a text filter (also known as an input filter); see section Installing the Text Filter . Furthermore, an output filter is actually more complex in that it has to examine the byte stream being sent to it for special flag characters and must send signals to itself on behalf of LPD.
However, an output filter is necessary if you want header pages and need to send escape sequences or other initialization strings to be able to print the header page. (But it's also futile if you want to charge header pages to the requesting user's account, since LPD doesn't give any user or host information to the output filter.)
On a single printer, LPD allows both an output filter and text or other filters. In such cases, LPD will start the output filter to print the header page (see section Header Pages ) only. LPD then expects the output filter to stop itself by sending two bytes to the filter: ASCII 031 followed by ASCII 001. When an output filter sees these two bytes (031, 001), it should stop by sending SIGSTOP to itself. When LPD's done running other filters, it'll restart the output filter by sending SIGCONT to it.
If there's an output filter but no text filter and LPD is working on a plain text job, LPD uses the output filter to do the job. As stated before, the output filter will print each file of the job in sequence with no intervening form feeds or other paper advancement, and this is probably not what you want. In almost all cases, you need a text filter.
The program lpf
, whch we introduced earlier as a text
filter, can also run as an output filter. If you need a
quick-and-dirty output filter but don't want to write the
byte detection and signal sending code, try lpf
. You
can also wrap lpf
in a shell script to handle any
intialization codes the printer might require.
lpf
: a Text Filter The program /usr/libexec/lpr/lpf
that comes
with FreeBSD binary distribution is a text filter (input
filter) that can indent output (job submitted with lpr
-i
), allow literal characters to pass (job submitted with
lpr -l
), adjust the printing position for backspaces
and tabs in the job, and account for pages printed. It
can also act like an output filter.
lpf
is suitable for many printing environments. And
although it has no capability to send initialization
sequences to a printer, it's easy to write a shell script
to do the needed initialization and then execute lpf
.
In order for lpf
to do page accounting correctly, it
needs correct values filled in for the pw
and pl
capabilities in the /etc/printcap
file. It uses
these values to determine how much text can fit on a page
and how many pages were in a user's job. For more
information on printer accounting, see
Accounting for Printer Usage
.