One of the major problems that face new users to UNIX printing is when they have a printer that has a proprietary print job format, such as the HP DeskJet series of printers. The solution to this problem is quite simple: generate your output in PostScript, and then use the GhostScript program to convert the GhostScript output to a format compatible with your printer.
#!/bin/sh /usr/local/bin/gs -dSAFER -dNOPAUSE -q -sDEVICE=djet500 \ -sOutputFile=- - && exit 0 exit 2
This simple tutorial example suffers from some serious problems. If you accidentally send a non-PostScript file to the printer GhostScript will detect this and exit with an error message but only after trying to interpret the input file as PostScript. If the input file was a text file, this can result in literally thousands of error messages and hundreds of pages of useless output.
In order to make a more robust filter we need to meet the following minimum requirements:
The file type should be determined, and only files that are PostScript should be passed to GhostScript.
We may have some conversion routines that can convert files into PostScript files and then we can send them to GhostScript for raster conversion.
If we cannot convert a file, then we should simply terminate the printing and cause the spooler to remove the job.
The ifhp Print Filter program is a companion to the LPRng software and does this type of operation. In addition, There are several other utilities that can be used. The magicfilter developed by H. Peter Anvin http://www.debian.org is distributed with Debian Linux. The apsfilter by Andreas Klemm http://www.freebsd.org/~andreas/start.html is also widely used, although now most of its functionality is directly available in LPRng. Finally, the a2ps (Ascii to PostScript) converter by Akim Demaille and Miguel Santana is available from www-inf.enst.fr/~demaille/a2ps. This package provides a very nice set of facilities for massaging, mangling, bending, twisting, and being downright nasty with text or other files.
Since this is a tutorial, we will demonstrate a simple way to make your own multi-format print filter, and provide insite into how these more complex packages work.
The file utility developed by Ian F. Darwin uses a database of file signatures to determine what the contents of a file are. For example:
h4: {195} % cd /tmp h4: {196} % echo hi >hi h4: {197} % gzip -c hi >hi.gz h4: {198} % echo "%!PS-Adobe-3.0" >test.ps h4: {199} % gzip -c test.ps >test.ps.gz h4: {200} % file hi hi.gz test.ps test.ps.gz hi: ASCII text hi.gz: gzip compressed data, deflated test.ps: PostScript document text conforming at level 3.0 test.ps.gz: gzip compressed data, deflated h4: {201} % file - <test.ps standard input: PostScript document text conforming at level 3.0
If we are given a file, we can now use file to recognize the file type and if the file type is suitable for our printer we can send it to the printer, otherwise we can reject it. The following is a simple yet very powerful shell script that does this.
#!/bin/sh # set up converters gs="/usr/local/bin/gs -dSAFER -dNOPAUSE -q -sDEVICE=djet500 \ -sOutputFile=/dev/fd/3 - 3>&1 1>&2" a2ps="/usr/local/bin/a2ps -q -B -1 -M Letter --borders=no -o-" decompress="" # get the file type type=`file - | tr A-Z a-z | sed -e 's/ */_/g'`; echo TYPE $type >&2 case "$type" in *gzip_compressed* ) decompress="gunzip -c |" compressed="compressed" ;; esac # we need to rewind the file perl -e "seek STDIN, 0, 0;" if test "X$decompress" != "X" ; then type=`$decompress head | file - | tr A-Z a-z | sed -e 's/ */_/g'`; echo COMPRESSED TYPE $type >&2 # we need to rewind the file perl -e "seek STDIN, 0, 0;" fi case "$type" in *postscript* ) process="$gs" ;; *text* ) process="$a2ps | $gs" ;; * ) echo "Cannot print type $compressed '$type'" >&2 # exit with JREMOVE status exit 3 ;; esac # in real life, replace 'echo' with 'exec' echo "$decompress $process" # exit with JABORT if this fails exit 2
Copy this to the /tmp/majik file, and give it 0755 (executable) permissions. Here is an example of the output of the script:
h4: {202} % /tmp/majik <test.ps.gz TYPE standard_input:_gzip_compressed_data,_deflated... COMPRESSED TYPE standard_input:_postscript_document_text_conforming_at_level_3.0 gunzip -c | /usr/local/bin/gs -dSAFER -dNOPAUSE -q -sDEVICE=djet500 \ -sOutputFile=/dev/fd/3 - 3>&1 1>&2 h4: {203} % /tmp/majik </tmp/hi TYPE standard_input:_ascii_text /usr/local/bin/a2ps -q -B -1 -M Letter --borders=no -o- \ | /usr/local/bin/gs -dSAFER -dNOPAUSE -q -sDEVICE=djet500 \ -sOutputFile=/dev/fd/3 - 3>&1 1>&2
The first part of the script sets up a standard set of commands that we will use in the various conversions. A full blown package for conversion would use a database or setup file to get these values. We then use the file utility to determine the input file type. The output of the file utility is translated to lower case and multiple blanks and tabs are removed.
We use a simple shell case statement to determine if we have a compressed file and get a decompression program to use. We reapply the file utility to the decompressed file (if it was compressed) and get the file type.
Finally we use another case statement to get the output converter and then we run the command. For tutorial purposes, we use an echo rather than an exec so we can see the actual command, rather than the output.
Just for completeness, here is majikperl:
#!/usr/bin/perl eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if $running_under_some_shell; # this emulates #! processing on NIH machines. # (remove #! line above if indigestible) my($gs) = "/usr/local/bin/gs -dSAFER -dNOPAUSE -q -sDEVICE=djet500 \ -sOutputFile=/dev/fd/3 - 3>&1 1>&2"; my($a2ps)="/usr/local/bin/a2ps -q -B -1 -M Letter --borders=no -o-"; my($decompress,$compressed,$process,$type); $decompress=$compressed=$process=$type=""; # get the file type $type = ` file - `; $type =~ tr /A-Z/a-z/; $type =~ s/\s+/_/g; print STDERR "TYPE $type\n"; ($decompress,$compressed) = ("gunzip -c |", "gzipped") if( $type =~ /gzip_compressed/ ); print STDERR "decompress $decompress\n"; unless( seek STDIN, 0, 0 ){ print "seek STDIN failed - $!\n"; exit 2; } if( $decompress ne "" ){ $type = ` $decompress file - `; $type =~ tr /A-Z/a-z/; $type =~ s/\s+/_/g; print STDERR "COMPRESSED TYPE $type\n"; unless( seek STDIN, 0, 0 ){ print "seek STDIN failed - $!\n"; exit 2; } } $_ = $type; if( /postscript/ ){ $process="$gs"; } elsif( /_text_/ ){ $process="$a2ps | $gs" ;; } else { print STDERR "Cannot print $compressed '$type'" >&2; # JREMOVE exit 3; } exec "$decompress $process"; print "exec failed - $!\n"; exit 2;