This document covers the use of GhostScript to write an LPR filter in order to print PostScript files with non-PostCript printer, like the recent cheap bubble-jet ones.
I own a (quite old) Canon BJ-230 printer. It's a parallel one and works well when printing text-only documents. However, it is sometimes necessary to print things like graphs or special fonts. I need PostScript support.
GhostScript knows how to print PostScript to many printers, including mine. The BJ-230 printer is quite the same as the BJ-200. The driver used by gs for this series of printers is bj200. The following command can be used to print a file.
# gs -sDEVICE=bj200 -dNOPAUSE -dBATCH -sOutputFile=/dev/lp0 FILE GNU Ghostscript 7.05 (2002-04-22) Copyright (C) 2002 artofcode LLC, Benicia, CA. All rights reserved. This software comes with NO WARRANTY: see the file PUBLIC for details. (...)
The -dNOPAUSE parameters prevents gs from waiting after each page sent to the printer, while -dBATCH tells it to exit after processing the file.
This method works, but has several non ignorable flaws among which:
There should be better solutions...
LPRng provides the usual command line tools like lpr, lpstat,... Most UNIX applications call these tools when printing is needed. It would be a Good Thing to print PostScript files directly using these commands.
LPRng uses the systemwide printer configuration file /etc/printcap. One interesting feature is the ability to specify filters. lpd usually pipes the file to print directly to the printer device.
A filter is a script which takes its input on stdin and outputs the filtered data to stdout. It would be nice to write a filter which take the PostScript file as its input and outputs the file in a language understandable by the printer. This is something GhostScript can do. A filter like the following can be written (I put this in /usr/libexec/filters/gsprint).
#!/bin/bash /usr/bin/gs -q -sDEVICE=bj200 -dNOPAUSE -dBATCH -sOutputFile=- -
Notice the new parameter -q parameter. It prevents gs from outputing its banner which may confuse the printer. Both the input and output files are -, which mean GhostScript reads and write data using the standard input and output.
The default entry for the printer in /etc/printcap is:
lp:\ :lp=/dev/lp0:\ :sd=/var/spool/lpd:\ :fq=true:\ :sh
In short, the lp option specifies the device, the sd one sets the spool directory (which will receive the files to be printed), the fq one asks for a linefeed to be sent to the printer after the end of each job, and the final sh option prevent the headers and footers to be printed with each document.
I don't want to modify this so that I can still lpr text files directly. I need to add a new print queue which will use my filter.
Each print queue needs its own directory. They're usually located in /var/spool. The new printer will be called lpps, so the new directory will be (totally arbitrary) called lpdps and be owned by lp:lp with permissions 0611. The entry corresponding to this print queue can then be added to the printcap file under the default lp entry.
lpps:\ :sd=/var/spool/lpdps:\ :fq=true:\ :if=/usr/libexec/filters/gsprint:\ :lp=/dev/lp0:\ :sh
The if one specifies the input filter.
Printing to the lpps queue will only work correctly with PostScript files as GhostScript will complain if its input is not in the good language.
$ lpr -P lpps FILE
Another useful utility is a2ps (Any To PostScript), which does exactly what its name says: convert anything to PostScript. It is particularly interesting to print text files, which it will format and print. We'll create an new print queue using this tool to print nice looking text files. Let's do it the hard way.
# mkdir /var/spool/lpda2ps # chown lp.lp /var/spool/lpda2ps # chmod 0611 /var/spool/lpda2ps # cat >> /etc/printcap lpa2ps:\ :sd=/var/spool/lpda2ps:\ :fq=true:\ :if=/usr/bin/a2ps -o - | /usr/libexec/filters/gsprint:\ :lp=/dev/lp0:\ :sh ^D
The filter is a little more complicated, as the input of the filter is piped into a2ps which (PostScript) output is then piped into our GhostScript based filter.
This solution works, but is not optimal. It is still necessary for the user to determine which print queue he should use depending on the type of the file. It should be interesting to write a better, more complex, filter which, depending on the type of its input, will process the data and output it through the correct program if needed to get on paper what the user intended to see. Let's do this now !
We mainly work (or at least I do) with 3 types of documents: ASCII text, PDF and PostScript files.
PDF and PostScript file can be recognized by their header (%!PS-Adobe-x.x for PostScript files or %PDF-x.x for PDF, where x.x is the version of the format). Knowing this, it is then possible to analyze a stream and determine what the format is. We'll assume that if the stream is neither PostScript nor PDF, it is an ASCII text.
Instead of blindly piping data into GhostScript, the script must have a look at the data. Data coming from stdin will be stored in a temporary file, created with mktemp to avoid race problems. Then the first line of the file will be read to determine the filetype. Finally, depending on the filetype, the file will be sent to the printer through gs, or directly (or maybe throught a2ps).
#!/bin/bash # # gsprint.sh, a GhostScript based filter for non PostScript printers # Copyright (c) Olivier Mehani <shtrom-skb@ssji.net>, 2004 # # This script is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Parts of this script were inspired by Swecoin's TTP8200 Unix drivers # (http://www.swecoinus.com). # # gs driver to use GSDEVICE=bj200 # should we use a2ps for plain text files ? USEA2PS=yes MKTEMP=/usr/bin/mktemp LOGGER=/usr/bin/logger CAT=/bin/cat HEAD=/bin/head COLRM=/usr/bin/colrm GS=/usr/bin/gs A2PS=/usr/bin/a2ps GSARGS="-q -sDEVICE=$GSDEVICE -dNOPAUSE -dBATCH -sOutputFile=-" TEMPFILE=`$MKTEMP /tmp/gsprint.XXXXXX" if [ $? -ne 0 ]; then $LOGGER -t gsprint "Error: couldn't create temporary file" exit 1 fi # send stdin to the temporary file cat > $TEMPFILE if [ "`$HEAD -1 $TEMPFILE | $COLRM 3`" = "%!" ]; then # PostScript file $GS $GSARGS $TEMPFILE elif [ "`$HEAD -1 $TEMPFILE | $COLRM 3`" = "%PDF" ]; then # PDF file $GS $GSARGS $TEMPFILE else # none of the above, we assume it is a text file if [ "$USEA2PS" == "yes" ]; then $A2PS $TEMPFILE -o - | $GS $GSARGS - else # no filtering cat $TEMPFILE fi fi # cleanup rm $TEMPFILE
I couldn't test this script, I have no ink left in my printer... It may (hope so) or may not work as is. If you had to make modifications for the filter to work, please let me know.