This section describes filters for printing specially formatted files, header pages,
printing across networks, and restricting and accounting for printer usage.
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 are not, they are
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 is about all it does.) There are also several
filters and filter components in the FreeBSD Ports Collection.
Here is what you will find in this section:
Section How Filters
Work, tries to give an overview of a filter's role in the printing process. You
should read this section to get an understanding of what is happening “under the
hood” when LPD uses filters. This knowledge could help
you anticipate and debug problems you might encounter as you install more and more
filters for each of your printers.
LPD expects every printer to be able to print plain text by
default. This presents a problem for PostScript®
printers (or other language-based printers) which cannot directly print plain text.
Section Accommodating
Plain Text Jobs on PostScript Printers tells you what
you should do to overcome this problem. You should read this section if you have a PostScript printer.
PostScript is a popular output format for many
programs. Some people even write PostScript code directly.
Unfortunately, PostScript printers are expensive. Section
Simulating PostScript on Non PostScript
Printers tells how you can further modify a printer's text filter to accept and print
PostScript data on a non PostScript printer. You
should read this section if you do not have a PostScript
printer.
Section Conversion
Filters tells about a way you can automate the conversion of specific file formats,
such as graphic or typesetting data, into formats your printer can understand. After
reading this section, you should be able to set up your printers such that users can type
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. The reading of
this section is recommended.
Section Output Filters tells
all about a not often used feature of LPD: output filters.
Unless you are printing header pages (see Header Pages), you can
probably skip that section altogether.
Section lpf: a Text Filter
describes 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.
Note: A copy of the various scripts described below can be found in the /usr/share/examples/printing directory.
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 is listed in the /etc/printcap file and what arguments
the user specified for the job on the lpr(1) 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 of filters you can specify in /etc/printcap:
The text filter, confusingly
called the input filter in LPD documentation, handles regular text printing. Think of it as
the default filter. LPD expects every printer to be able to
print plain text by default, and it is the text filter's job to make sure backspaces,
tabs, or other special characters do not confuse the printer. If you are in an
environment where you have to account for printer usage, the text filter must also
account for pages printed, usually by counting the number of lines printed and comparing
that to the number of lines per page the printer supports. The text filter is started
with the following argument list:
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.
A conversion filter converts a
specific file format into one the printer can render onto paper. For example, ditroff
typesetting data cannot be directly printed, but you can install a conversion filter for
ditroff files to convert the ditroff data into a form the printer can digest and print.
Section Conversion
Filters tells all about them. Conversion filters also need to do accounting, if you
need printer accounting. Conversion filters are started with the following arguments:
where pixel-width is the value from the px capability (default 0) and pixel-height is the value from the py capability (default 0).
The output filter is used only
if there is no text filter, or if header pages are enabled. In our experience, output
filters are rarely used. Section Output Filters describes them.
There are only two arguments to an output filter:
filter-name -w width -l
length
which are identical to the text filters -w and -l arguments.
Filters should also exit with
the following exit status:
exit 0
If the filter printed the file successfully.
exit 1
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.
exit 2
If the filter failed to print the file and does not 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 are shopping for filters, see if they are 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 are 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 do not 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 are 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
are not 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 have 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 is part of the FreeBSD Ports Collection (see The Ports Collection). You can install one of the both print/lprps-a4 and print/lprps-letter ports according to the paper size used.
After installing lprps, just specify the pathname to the psif program that is 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:
The rw capability should be also included in order to let LPD to open the printer in the read-write mode.
If you have a parallel PostScript printer (and
therefore cannot 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
#
IFS="" read -r 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
In the above script, 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, Aladdin Enterprises has a
free PostScript work-alike 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 is in the FreeBSD Ports Collection, many
versions are available, the most commonly used version is print/ghostscript-gpl.
To simulate PostScript, we have the text filter detect
if it is printing a PostScript file. If it is 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 is 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 DeskJet 500
# Installed in /usr/local/libexec/ifhp
#
# Treat LF as CR+LF (to avoid the "staircase effect" on HP/PCL
# printers):
#
printf "\033&k2G" || exit 2
#
# Read first two characters of the file
#
IFS="" read -r first_line
first_two_chars=`expr "$first_line" : '\(..\)'`
if [ "$first_two_chars" = "%!" ]; then
#
# It is 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 feed
# at the end to eject the last page.
#
echo "$first_line" && cat && printf "\033&l0H" &&
exit 0
fi
exit 2
Finally, you need to notify LPD of the filter via the if capability:
:if=/usr/local/libexec/ifhp:
That is 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 will 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 cannot print it directly until we convert
the DVI file into PostScript. The command sequence goes
like this:
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 are just one step away from printing it:
%lpr -dseaweed-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 have 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:
File type
/etc/printcap capability
lpr option
cifplot
cf
-c
DVI
df
-d
plot
gf
-g
ditroff
nf
-n
FORTRAN text
rf
-f
troff
tf
-f
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 would 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
are specialized programs that only LPD will run; regular users
should not 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 will add the DVI conversion filter to the entry for the printer
named bamboo. Here is 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:ms#-parenb cs8 clocal crtscts:rw:\
:if=/usr/local/libexec/psif:\
:df=/usr/local/libexec/psdf:
The DVI filter is a shell script named /usr/local/libexec/psdf. Here is that script:
#!/bin/sh
#
# psdf - 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 "$@"
This script runs 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 Accommodating Plain Text
Jobs on PostScript Printers) with the arguments LPD passed to this script. The lprps
utility will use those arguments to account for the pages printed.
There is no fixed set of steps to install conversion filters, some working examples
are described in this section. 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:
It works by converting the GIF file into a portable anymap, converting that into a
portable graymap, converting that into a portable bitmap, and converting that into
LaserJet/PCL-compatible data.
Here is the /etc/printcap file with an entry for a printer
using the above filter:
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 "$@"
The above script makes use of lprps again to handle the
communication with the printer. If the printer were on a parallel port, we would use this
script instead:
#!/bin/sh
#
# pstf - Convert groff's troff data into PS, then print.
# Installed in /usr/local/libexec/pstf
#
exec grops
That is it. Here is the entry we need to add to /etc/printcap to enable the filter:
:tf=/usr/local/libexec/pstf:
Here is an example that might make old hands at FORTRAN blush. It is a FORTRAN-text
filter for any printer that can directly print plain text. We will 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 "\033&l0H" &&
exit 0
exit 2
And we will add this line to the /etc/printcap for the
printer teak to enable this filter:
:rf=/usr/local/libexec/hprf:
Here is one final, somewhat complex example. We will 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: print/dvi2xx. Installing this port gives us the program we
need, dvilj2p, which converts DVI into LaserJet IIp, LaserJet
III, and LaserJet 2000 compatible codes.
The dvilj2p utility makes the filter hpdf quite complex since dvilj2p cannot
read from standard input. It wants to work with a filename. What is 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 cannot 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 will not 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 is (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 do not 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 are 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 does not 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(1) command line)
which one to use. If your users are not particularly computer literate, having to specify
a filter option will become annoying. What is 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 is the default filter) detect the type of file it has been asked to
print and then automatically run the right conversion filter. Tools such as file can be of help here. Of course, it will 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 (print/apsfilter). It can detect plain text, PostScript, DVI and almost any kind of files, run the proper
conversions, and print.
The LPD spooling system supports one other type of filter
that we have 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 are
using an output filter but no text filter, then:
LPD starts an output filter once for the entire job instead
of once for each file in the job.
LPD does not make any provision to identify the start or
the end of files within the job for the output filter.
LPD does not pass the user's login or host to the filter,
so it is not intended to do accounting. In fact, it gets only two arguments:
filter-name -wwidth -llength
Where width is from the pw capability and length is from
the pl capability for the printer in question.
Do not be seduced by an output filter's simplicity. If you would like each file in a
job to start on a different page an output filter will not 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 is
also futile if you want to charge
header pages to the requesting user's account, since LPD does
not 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 will restart the output
filter by sending SIGCONT to it.
If there is 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, which we introduced earlier as a text
filter, can also run as an output filter. If you need a quick-and-dirty output filter but
do not want to write the byte detection and signal sending code, try lpf. You can also wrap lpf in a shell
script to handle any initialization codes the printer might require.
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.
The lpf filter is suitable for many printing environments.
And although it has no capability to send initialization sequences to a printer, it is
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.
If you have lots of users, all
of them using various printers, then you probably want to consider header pages as a necessary evil.
Header pages, also known as banner or burst
pages identify to whom jobs belong after they are printed. They are usually
printed in large, bold letters, perhaps with decorative borders, so that in a stack of
printouts they stand out from the real documents that comprise users' jobs. They enable
users to locate their jobs quickly. The obvious drawback to a header page is that it is
yet one more sheet that has to be printed for every job, their ephemeral usefulness
lasting not more than a few minutes, ultimately finding themselves in a recycling bin or
rubbish heap. (Note that header pages go with each job, not each file in a job, so the
paper waste might not be that bad.)
The LPD system can provide header pages automatically for
your printouts if your printer can
directly print plain text. If you have a PostScript
printer, you will need an external program to generate the header page; see Header Pages on PostScript Printers.
In the Simple Printer Setup
section, we turned off header pages by specifying sh (meaning
“suppress header”) in the /etc/printcap file. To
enable header pages for a printer, just remove the sh
capability.
Sounds too easy, right?
You are right. You might have to
provide an output filter to send initialization strings to the printer. Here is an
example output filter for Hewlett Packard PCL-compatible printers:
Now, when users print jobs to teak, they get a header page
with each job. If users want to spend time searching for their printouts, they can
suppress header pages by submitting the job with lpr -h; see the Header Page Options section for
more lpr(1) options.
Note:LPD prints a form feed character after the
header page. If your printer uses a different character or sequence of characters to
eject a page, specify them with the ff capability in /etc/printcap.
By enabling header pages, LPD will produce a long header, a full page of large letters
identifying the user, host, and job. Here is an example (kelly
printed the job named “outline” from host rose):
k ll ll
k l l
k l l
k k eeee l l y y
k k e e l l y y
k k eeeeee l l y y
kk k e l l y y
k k e e l l y yy
k k eeee lll lll yyy y
y
y y
yyyy
ll
t l i
t l
oooo u u ttttt l ii n nnn eeee
o o u u t l i nn n e e
o o u u t l i n n eeeeee
o o u u t l i n n e
o o u uu t t l i n n e e
oooo uuu u tt lll iii n n eeee
r rrr oooo ssss eeee
rr r o o s s e e
r o o ss eeeeee
r o o ss e
r o o s s e e
r oooo ssss eeee
Job: outline
Date: Sun Sep 17 11:04:58 1995
LPD appends a form feed after this text so the job starts
on a new page (unless you have sf (suppress form feeds) in the
destination printer's entry in /etc/printcap).
If you prefer, LPD can make a short header; specify sb (short
banner) in the /etc/printcap file. The header page will look
like this:
rose:kelly Job: outline Date: Sun Sep 17 11:07:51 1995
Also by default, LPD prints the header page first, then the
job. To reverse that, specify hl (header last) in /etc/printcap.
Using LPD's built-in header pages enforces a particular
paradigm when it comes to printer accounting: header pages must be free of charge.
Why?
Because the output filter is the only external program that will have control when the
header page is printed that could do accounting, and it is not provided with any user or host information or an accounting
file, so it has no idea whom to charge for printer use. It is also not enough to just
“increase the page count by one” by modifying the text filter or any of the
conversion filters (which do have user and host information) since users can suppress
header pages with lpr -h. They could
still be charged for header pages they did not print. Basically, lpr
-h will be the preferred option of
environmentally-minded users, but you cannot offer any incentive to use it.
It is still not enough to have
each of the filters generate their own header pages (thereby being able to charge for
them). If users wanted the option of suppressing the header pages with lpr -h, they will still get them and be
charged for them since LPD does not pass any knowledge of the
-h option to any of the filters.
So, what are your options?
You can:
Accept LPD's paradigm and make header pages free.
Install an alternative to LPD, such as LPRng. Section Alternatives to the Standard Spooler tells more
about other spooling software you can substitute for LPD.
Write a smart output filter.
Normally, an output filter is not meant to do anything more than initialize a printer or
do some simple character conversion. It is suited for header pages and plain text jobs
(when there is no text (input) filter). But, if there is a text filter for the plain text
jobs, then LPD will start the output filter only for the
header pages. And the output filter can parse the header page text that LPD generates to determine what user and host to charge for the
header page. The only other problem with this method is that the output filter still does
not know what accounting file to use (it is not passed the name of the file from the af capability), but if you have a well-known accounting file, you
can hard-code that into the output filter. To facilitate the parsing step, use the sh (short header) capability in /etc/printcap. Then again, all that might be too much trouble, and
users will certainly appreciate the more generous system administrator who makes header
pages free.
As described above, LPD can generate a plain text header
page suitable for many printers. Of course, PostScript
cannot directly print plain text, so the header page feature of LPD is useless--or mostly so.
One obvious way to get header pages is to have every conversion filter and the text
filter generate the header page. The filters should use the user and host arguments to
generate a suitable header page. The drawback of this method is that users will always
get a header page, even if they submit jobs with lpr -h.
Let us explore this method. The following script takes three arguments (user login
name, host name, and job name) and makes a simple PostScript header page:
#!/bin/sh
#
# make-ps-header - make a PostScript header page on stdout
# Installed in /usr/local/libexec/make-ps-header
#
#
# These are PostScript units (72 to the inch). Modify for A4 or
# whatever size paper you are using:
#
page_width=612
page_height=792
border=72
#
# Check arguments
#
if [ $# -ne 3 ]; then
echo "Usage: `basename $0` <user> <host> <job>" 1>&2
exit 1
fi
#
# Save these, mostly for readability in the PostScript, below.
#
user=$1
host=$2
job=$3
date=`date`
#
# Send the PostScript code to stdout.
#
exec cat <<EOF
%!PS
%
% Make sure we do not interfere with user's job that will follow
%
save
%
% Make a thick, unpleasant border around the edge of the paper.
%
$border $border moveto
$page_width $border 2 mul sub 0 rlineto
0 $page_height $border 2 mul sub rlineto
currentscreen 3 -1 roll pop 100 3 1 roll setscreen
$border 2 mul $page_width sub 0 rlineto closepath
0.8 setgray 10 setlinewidth stroke 0 setgray
%
% Display user's login name, nice and large and prominent
%
/Helvetica-Bold findfont 64 scalefont setfont
$page_width ($user) stringwidth pop sub 2 div $page_height 200 sub moveto
($user) show
%
% Now show the boring particulars
%
/Helvetica findfont 14 scalefont setfont
/y 200 def
[ (Job:) (Host:) (Date:) ] {
200 y moveto show /y y 18 sub def }
forall
/Helvetica-Bold findfont 14 scalefont setfont
/y 200 def
[ ($job) ($host) ($date) ] {
270 y moveto show /y y 18 sub def
} forall
%
% That is it
%
restore
showpage
EOF
Now, each of the conversion filters and the text filter can call this script to first
generate the header page, and then print the user's job. Here is the DVI conversion
filter from earlier in this document, modified to make a header page:
#!/bin/sh
#
# psdf - DVI to PostScript printer filter
# Installed in /usr/local/libexec/psdf
#
# Invoked by lpd when user runs lpr -d
#
orig_args="$@"
fail() {
echo "$@" 1>&2
exit 2
}
while getopts "x:y:n:h:" option; do
case $option in
x|y) ;; # Ignore
n) login=$OPTARG ;;
h) host=$OPTARG ;;
*) echo "LPD started `basename $0` wrong." 1>&2
exit 2
;;
esac
done
[ "$login" ] || fail "No login name"
[ "$host" ] || fail "No host name"
( /usr/local/libexec/make-ps-header $login $host "DVI File"
/usr/local/bin/dvips -f ) | eval /usr/local/libexec/lprps $orig_args
Notice how the filter has to parse the argument list in order to determine the user
and host name. The parsing for the other conversion filters is identical. The text filter
takes a slightly different set of arguments, though (see section How Filters Work).
As we have mentioned before, the above scheme, though fairly simple, disables the
“suppress header page” option (the -h option) to
lpr. If users wanted to save a tree (or a few pennies, if you
charge for header pages), they would not be able to do so, since every filter's going to
print a header page with every job.
To allow users to shut off header pages on a per-job basis, you will need to use the
trick introduced in section Accounting for
Header Pages: write an output filter that parses the LPD-generated header page and
produces a PostScript version. If the user submits the job
with lpr -h, then LPD will not generate a header page, and neither will your output
filter. Otherwise, your output filter will read the text from LPD and send the appropriate header page PostScript code to the printer.
If you have a PostScript printer on a serial line, you
can make use of lprps, which comes with an output filter, psof, which does the above. Note that psof
does not charge for header pages.
FreeBSD supports networked printing: sending jobs to remote printers. Networked
printing generally refers to two different things:
Accessing a printer attached to a remote host. You install a printer that has a
conventional serial or parallel interface on one host. Then, you set up LPD to enable access to the printer from other hosts on the
network. Section Printers
Installed on Remote Hosts tells how to do this.
Accessing a printer attached directly to a network. The printer has a network
interface in addition to (or in place of) a more conventional serial or parallel
interface. Such a printer might work as follows:
It might understand the LPD protocol and can even queue
jobs from remote hosts. In this case, it acts just like a regular host running LPD. Follow the same procedure in section Printers Installed on Remote
Hosts to set up such a printer.
It might support a data stream network connection. In this case, you
“attach” the printer to one host on the network by making that host
responsible for spooling jobs and sending them to the printer. Section Printers with Networked
Data Stream Interfaces gives some suggestions on installing such printers.
The LPD spooling system has built-in support for sending
jobs to other hosts also running LPD (or are compatible with
LPD). This feature enables you to install a printer on one
host and make it accessible from other hosts. It also works with printers that have
network interfaces that understand the LPD protocol.
To enable this kind of remote printing, first install a printer on one host, the printer host, using the simple printer
setup described in the Simple Printer
Setup section. Do any advanced setup in Advanced
Printer Setup that you need. Make sure to test the printer and see if it works with
the features of LPD you have enabled. Also ensure that the
local host has authorization to use
the LPD service in the remote host (see Restricting Jobs from
Remote Hosts).
If you are using a printer with a network interface that is compatible with LPD, then the printer
host in the discussion below is the printer itself, and the printer name is the name you configured
for the printer. See the documentation that accompanied your printer and/or
printer-network interface.
Tip: If you are using a Hewlett Packard Laserjet then the printer name text will automatically perform the LF to CRLF conversion for you,
so you will not require the hpif script.
Then, on the other hosts you want to have access to the printer, make an entry in
their /etc/printcap files with the following:
Name the entry anything you want. For simplicity, though, you probably want to use the
same name and aliases as on the printer host.
Leave the lp capability blank, explicitly (:lp=:).
Make a spooling directory and specify its location in the sd
capability. LPD will store jobs here before they get sent to
the printer host.
Place the name of the printer host in the rm capability.
Place the printer name on the printer
host in the rp capability.
That is it. You do not need to list conversion filters, page dimensions, or anything
else in the /etc/printcap file.
Here is an example. The host rose has two printers, bamboo and rattan. We will enable users on
the host orchid to print to those printers. Here is the /etc/printcap file for orchid (back from
section Enabling
Header Pages). It already had the entry for the printer teak; we have added entries for the two printers on the host rose:
#
# /etc/printcap for host orchid - added (remote) printers on rose
#
#
# teak is local; it is connected directly to orchid:
#
teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\
:lp=/dev/lpt0:sd=/var/spool/lpd/teak:mx#0:\
:if=/usr/local/libexec/ifhp:\
:vf=/usr/local/libexec/vfhp:\
:of=/usr/local/libexec/ofhp:
#
# rattan is connected to rose; send jobs for rattan to rose:
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
:lp=:rm=rose:rp=rattan:sd=/var/spool/lpd/rattan:
#
# bamboo is connected to rose as well:
#
bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
:lp=:rm=rose:rp=bamboo:sd=/var/spool/lpd/bamboo:
Then, we just need to make spooling directories on orchid:
Now, users on orchid can print to rattan and bamboo. If, for example, a user
on orchid typed:
%lpr -P bamboo -dsushi-review.dvi
the LPD system on orchid would copy
the job to the spooling directory /var/spool/lpd/bamboo and
note that it was a DVI job. As soon as the host rose has room in
its bamboo spooling directory, the two LPDs would transfer the file to rose. The
file would wait in rose's queue until it was finally printed. It
would be converted from DVI to PostScript (since bamboo is a PostScript printer) on
rose.
Often, when you buy a network interface card for a printer, you can get two versions:
one which emulates a spooler (the more expensive version), or one which just lets you
send data to it as if you were using a serial or parallel port (the cheaper version).
This section tells how to use the cheaper version. For the more expensive one, see the
previous section Printers
Installed on Remote Hosts.
The format of the /etc/printcap file lets you specify what
serial or parallel interface to use, and (if you are using a serial interface), what baud
rate, whether to use flow control, delays for tabs, conversion of newlines, and more. But
there is no way to specify a connection to a printer that is listening on a TCP/IP or
other network port.
To send data to a networked printer, you need to develop a communications program that
can be called by the text and conversion filters. Here is one such example: the script
netprint takes all data on standard input and sends it to a
network-attached printer. We specify the hostname of the printer as the first argument
and the port number to which to connect as the second argument to netprint. Note that this supports one-way communication only
(FreeBSD to printer); many network printers support two-way communication, and you might
want to take advantage of that (to get printer status, perform accounting, etc.).
#!/usr/bin/perl
#
# netprint - Text filter for printer attached to network
# Installed in /usr/local/libexec/netprint
#
$#ARGV eq 1 || die "Usage: $0 <printer-hostname> <port-number>";
$printer_host = $ARGV[0];
$printer_port = $ARGV[1];
require 'sys/socket.ph';
($ignore, $ignore, $protocol) = getprotobyname('tcp');
($ignore, $ignore, $ignore, $ignore, $address)
= gethostbyname($printer_host);
$sockaddr = pack('S n a4 x8', &AF_INET, $printer_port, $address);
socket(PRINTER, &PF_INET, &SOCK_STREAM, $protocol)
|| die "Can't create TCP/IP stream socket: $!";
connect(PRINTER, $sockaddr) || die "Can't contact $printer_host: $!";
while (<STDIN>) { print PRINTER; }
exit 0;
We can then use this script in various filters. Suppose we had a Diablo 750-N line
printer connected to the network. The printer accepts data to print on port number 5100.
The host name of the printer is scrivener. Here is the text
filter for the printer:
#!/bin/sh
#
# diablo-if-net - Text filter for Diablo printer `scrivener' listening
# on port 5100. Installed in /usr/local/libexec/diablo-if-net
#
exec /usr/libexec/lpr/lpf "$@" | /usr/local/libexec/netprint scrivener 5100
This section gives information on restricting printer usage. The LPD system lets you control who can access a printer, both
locally or remotely, whether they can print multiple copies, how large their jobs can be,
and how large the printer queues can get.
The LPD system makes it easy for users to print multiple
copies of a file. Users can print jobs with lpr -#5 (for example) and get five copies of each file in the job.
Whether this is a good thing is up to you.
If you feel multiple copies cause unnecessary wear and tear on your printers, you can
disable the -# option to lpr(1) by adding the
sc capability to the /etc/printcap
file. When users submit jobs with the -# option, they will
see:
lpr: multiple copies are not allowed
Note that if you have set up access to a printer remotely (see section Printers Installed on Remote
Hosts), you need the sc capability on the remote /etc/printcap files as well, or else users will still be able to
submit multiple-copy jobs by using another host.
Here is an example. This is the /etc/printcap file for the
host rose. The printer rattan is quite
hearty, so we will allow multiple copies, but the laser printer bamboo is a bit more delicate, so we will disable multiple copies by
adding the sc capability:
#
# /etc/printcap for host rose - restrict multiple copies on 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:sc:\
:lp=/dev/ttyd5:ms#-parenb cs8 clocal crtscts:rw:\
:if=/usr/local/libexec/psif:\
:df=/usr/local/libexec/psdf:
Now, we also need to add the sc capability on the host orchid's /etc/printcap (and while we are at
it, let us disable multiple copies for the printer teak):
#
# /etc/printcap for host orchid - no multiple copies for local
# printer teak or remote printer bamboo
teak|hp|laserjet|Hewlett Packard LaserJet 3Si:\
:lp=/dev/lpt0:sd=/var/spool/lpd/teak:mx#0:sc:\
:if=/usr/local/libexec/ifhp:\
:vf=/usr/local/libexec/vfhp:\
:of=/usr/local/libexec/ofhp:
rattan|line|diablo|lp|Diablo 630 Line Printer:\
:lp=:rm=rose:rp=rattan:sd=/var/spool/lpd/rattan:
bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
:lp=:rm=rose:rp=bamboo:sd=/var/spool/lpd/bamboo:sc:
By using the sc capability, we prevent the use of lpr -#, but that still does not prevent
users from running lpr(1) multiple times,
or from submitting the same file multiple times in one job like this:
You can control who can print to what printers by using the UNIX® group mechanism and the rg
capability in /etc/printcap. Just place the users you want to
have access to a printer in a certain group, and then name that group in the rg capability.
If users outside the group (including root) try to print to
the controlled printer then they will be greeted with the following message:
lpr: Not a member of the restricted group
As with the sc (suppress multiple copies) capability, you
need to specify rg on remote hosts that also have access to your
printers, if you feel it is appropriate (see section Printers Installed on Remote
Hosts).
For example, we will let anyone access the printer rattan,
but only those in group artists can use bamboo. Here is the familiar /etc/printcap
for host rose:
#
# /etc/printcap for host rose - restricted group 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:sc:rg=artists:\
:lp=/dev/ttyd5:ms#-parenb cs8 clocal crtscts:rw:\
:if=/usr/local/libexec/psif:\
:df=/usr/local/libexec/psdf:
Let us leave the other example /etc/printcap file (for the
host orchid) alone. Of course, anyone on orchid can print to bamboo. It might be the
case that we only allow certain logins on orchid anyway, and want
them to have access to the printer. Or not.
Note: There can be only one restricted group per printer.
If you have many users accessing the printers, you probably need to put an upper limit
on the sizes of the files users can submit to print. After all, there is only so much
free space on the filesystem that houses the spooling directories, and you also need to
make sure there is room for the jobs of other users.
LPD enables you to limit the maximum byte size a file in a
job can be with the mx capability. The units are in BUFSIZ blocks, which are 1024 bytes. If you put a zero for this
capability, there will be no limit on file size; however, if no mx capability is specified, then a default limit of 1000 blocks will
be used.
Note: The limit applies to files in a job, and not the total job size.
LPD will not refuse a file that is larger than the limit
you place on a printer. Instead, it will queue as much of the file up to the limit, which
will then get printed. The rest will be discarded. Whether this is correct behavior is up
for debate.
Let us add limits to our example printers rattan and bamboo. Since those artists' PostScript files tend to be large, we will limit them to five
megabytes. We will put no limit on the plain text line printer:
#
# /etc/printcap for host rose
#
#
# No limit on job size:
#
rattan|line|diablo|lp|Diablo 630 Line Printer:\
:sh:mx#0:sd=/var/spool/lpd/rattan:\
:lp=/dev/lpt0:\
:if=/usr/local/libexec/if-simple:
#
# Limit of five megabytes:
#
bamboo|ps|PS|S|panasonic|Panasonic KX-P4455 PostScript v51.4:\
:sh:sd=/var/spool/lpd/bamboo:sc:rg=artists:mx#5000:\
:lp=/dev/ttyd5:ms#-parenb cs8 clocal crtscts:rw:\
:if=/usr/local/libexec/psif:\
:df=/usr/local/libexec/psdf:
Again, the limits apply to the local users only. If you have set up access to your
printers remotely, remote users will not get those limits. You will need to specify the
mx capability in the remote /etc/printcap files as well. See section Printers Installed on Remote
Hosts for more information on remote printing.
The LPD spooling system provides several ways to restrict
print jobs submitted from remote hosts:
Host restrictions
You can control from which remote hosts a local LPD accepts
requests with the files /etc/hosts.equiv and /etc/hosts.lpd. LPD checks to see if an
incoming request is from a host listed in either one of these files. If not, LPD refuses the request.
The format of these files is simple: one host name per line. Note that the file /etc/hosts.equiv is also used by the ruserok(3) protocol,
and affects programs like rsh(1) and rcp(1), so be
careful.
For example, here is the /etc/hosts.lpd file on the host rose:
orchid
violet
madrigal.fishbaum.de
This means rose will accept requests from the hosts orchid, violet, and madrigal.fishbaum.de. If any other host tries to access rose's LPD, the job will be refused.
Size restrictions
You can control how much free space there needs to remain on the filesystem where a
spooling directory resides. Make a file called minfree in the
spooling directory for the local printer. Insert in that file a number representing how
many disk blocks (512 bytes) of free space there has to be for a remote job to be
accepted.
This lets you insure that remote users will not fill your filesystem. You can also use
it to give a certain priority to local users: they will be able to queue jobs long after
the free disk space has fallen below the amount specified in the minfree file.
For example, let us add a minfree file for the printer bamboo. We examine /etc/printcap to find
the spooling directory for this printer; here is bamboo's
entry:
The spooling directory is given in the sd capability. We will
make three megabytes (which is 6144 disk blocks) the amount of free disk space that must
exist on the filesystem for LPD to accept remote jobs:
#echo 6144 > /var/spool/lpd/bamboo/minfree
User restrictions
You can control which remote users can print to local printers by specifying the rs capability in /etc/printcap. When rs appears in the entry for a locally-attached printer, LPD will accept jobs from remote hosts if the user submitting the job also has an account of the
same login name on the local host. Otherwise, LPD refuses the
job.
This capability is particularly useful in an environment where there are (for example)
different departments sharing a network, and some users transcend departmental
boundaries. By giving them accounts on your systems, they can use your printers from
their own departmental systems. If you would rather allow them to use only your printers and not your computer
resources, you can give them “token” accounts, with no home directory and a
useless shell like /usr/bin/false.
So, you need to charge for printouts. And why not? Paper and ink cost money. And then
there are maintenance costs--printers are loaded with moving parts and tend to break
down. You have examined your printers, usage patterns, and maintenance fees and have come
up with a per-page (or per-foot, per-meter, or per-whatever) cost. Now, how do you
actually start accounting for printouts?
Well, the bad news is the LPD spooling system does not
provide much help in this department. Accounting is highly dependent on the kind of
printer in use, the formats being printed, and your requirements in charging for printer usage.
To implement accounting, you have to modify a printer's text filter (to charge for
plain text jobs) and the conversion filters (to charge for other file formats), to count
pages or query the printer for pages printed. You cannot get away with using the simple
output filter, since it cannot do accounting. See section Filters.
Generally, there are two ways to do accounting:
Periodic accounting is the more
common way, possibly because it is easier. Whenever someone prints a job, the filter logs
the user, host, and number of pages to an accounting file. Every month, semester, year,
or whatever time period you prefer, you collect the accounting files for the various
printers, tally up the pages printed by users, and charge for usage. Then you truncate
all the logging files, starting with a clean slate for the next period.
Timely accounting is less
common, probably because it is more difficult. This method has the filters charge users
for printouts as soon as they use the printers. Like disk quotas, the accounting is
immediate. You can prevent users from printing when their account goes in the red, and
might provide a way for users to check and adjust their “print quotas”. But
this method requires some database code to track users and their quotas.
The LPD spooling system supports both methods easily: since
you have to provide the filters (well, most of the time), you also have to provide the
accounting code. But there is a bright side: you have enormous flexibility in your
accounting methods. For example, you choose whether to use periodic or timely accounting.
You choose what information to log: user names, host names, job types, pages printed,
square footage of paper used, how long the job took to print, and so forth. And you do so
by modifying the filters to save this information.
FreeBSD comes with two programs that can get you set up with simple periodic
accounting right away. They are the text filter lpf, described
in section lpf: a Text Filter,
and pac(8), a program to
gather and total entries from printer accounting files.
As mentioned in the section on filters (Filters), LPD starts the text and the conversion filters with the name of
the accounting file to use on the filter command line. The filters can use this argument
to know where to write an accounting file entry. The name of this file comes from the af capability in /etc/printcap, and if not
specified as an absolute path, is relative to the spooling directory.
LPD starts lpf with page width and
length arguments (from the pw and pl
capabilities). The lpf filter uses these arguments to determine
how much paper will be used. After sending the file to the printer, it then writes an
accounting entry in the accounting file. The entries look like this:
You should use a separate accounting file for each printer, as lpf has no file locking logic built into it, and two lpfs might corrupt each other's entries if they were to write to the
same file at the same time. An easy way to insure a separate accounting file for each
printer is to use af=acct in /etc/printcap. Then, each accounting file will be in the spooling
directory for a printer, in a file named acct.
When you are ready to charge users for printouts, run the pac(8) program. Just
change to the spooling directory for the printer you want to collect on and type pac. You will get a dollar-centric summary like the following:
Which printer to summarize. This option works only
if there is an absolute path in the af capability in /etc/printcap.
-c
Sort the output by cost instead of alphabetically by user name.
-m
Ignore host name in the accounting files. With this option, user smith on host alpha is the same user smith on host gamma. Without, they are
different users.
-pprice
Compute charges with price dollars per page or per
foot instead of the price from the pc capability in /etc/printcap, or two cents (the default). You can specify price as a floating point number.
-r
Reverse the sort order.
-s
Make an accounting summary file and truncate the accounting file.
name...
Print accounting information for the given user names only.
In the default summary that pac(8) produces, you
see the number of pages printed by each user from various hosts. If, at your site, host
does not matter (because users can use any host), run pac -m, to produce the following summary:
Login pages/feet runs price
andy 2.00 1 $ 0.04
kelly 182.00 105 $ 3.64
mary 118.00 35 $ 2.36
root 26.00 12 $ 0.52
zhang 9.00 1 $ 0.18
total 337.00 154 $ 6.74
To compute the dollar amount due, pac(8) uses the pc capability in the /etc/printcap file
(default of 200, or 2 cents per page). Specify, in hundredths of cents, the price per
page or per foot you want to charge for printouts in this capability. You can override
this value when you run pac(8) with the -p option. The units for the -p option
are in dollars, though, not hundredths of cents. For example,
#pac -p1.50
makes each page cost one dollar and fifty cents. You can really rake in the profits by
using this option.
Finally, running pac -s will save
the summary information in a summary accounting file, which is named the same as the
printer's accounting file, but with _sum appended to the name.
It then truncates the accounting file. When you run pac(8) again, it
rereads the summary file to get starting totals, then adds information from the regular
accounting file.
In order to perform even remotely accurate accounting, you need to be able to
determine how much paper a job uses. This is the essential problem of printer
accounting.
For plain text jobs, the problem is not that hard to solve: you count how many lines
are in a job and compare it to how many lines per page your printer supports. Do not
forget to take into account backspaces in the file which overprint lines, or long logical
lines that wrap onto one or more additional physical lines.
The text filter lpf (introduced in lpf: a Text Filter) takes into
account these things when it does accounting. If you are writing a text filter which
needs to do accounting, you might want to examine lpf's source
code.
How do you handle other file formats, though?
Well, for DVI-to-LaserJet or DVI-to-PostScript
conversion, you can have your filter parse the diagnostic output of dvilj or dvips and look to see how many
pages were converted. You might be able to do similar things with other file formats and
conversion programs.
But these methods suffer from the fact that the printer may not actually print all
those pages. For example, it could jam, run out of toner, or explode--and the user would
still get charged.
So, what can you do?
There is only one sure way to do
accurate accounting. Get a printer
that can tell you how much paper it uses, and attach it via a serial line or a network
connection. Nearly all PostScript printers support this
notion. Other makes and models do as well (networked Imagen laser printers, for example).
Modify the filters for these printers to get the page usage after they print each job and
have them log accounting information based on that value only. There is no line counting nor error-prone file
examination required.
Of course, you can always be generous and make all printouts free.