#!/usr/bin/perl -w =head1 NAME vpp - View and (selectively) Print PDF and PostScript =head1 SYNOPSIS vpp [options] [file] Options: Short: Defaults: --batch -b * --print -p true * --view -vi true * --quiet -q true --rc -r --help -h --version -ve =head1 INTRODUCTION B displays a B document or a B document (after conversion to B) using B, B, or B (in that order of choice), or perhaps another B viewer. One can use the viewer to print the document but, alternatively, leave the viewer and use B's facilities to print selected pages to a one- or two-sided hardcopy or an A5-booklet: see the section `Page selection' for the details. If F if not found, B looks for F or F. If both are found, B dies, saying that it can't make a choice, if one of the two is found it is used instead of F. If F lacks, standard input is used. =cut # exit values: # 0 OK # 1 error # 2 edit use strict; use Getopt::Long; use FileHandle; use IO::Handle; use Term::ReadLine; use File::Copy; use Config; use File::Spec::Functions qw/devnull catfile tmpdir rootdir path curdir/; our ($rc, $print, $view, $quiet, $batch,)= ('', 1, 1, 1, '', ); our ($version, $viewer) = (0, '' ); handle_options( "batch=s" => \$batch, "version" => \&version, "view!" => \$view, "quiet!" => \$quiet, "print!" => \$print, "rc=s" => \$rc, "help" => \&help, ); executable($viewer) or $viewer = executable('gv') || executable('xpdf') || executable('acroread',2) || undef; if ($batch) { # $batch must contain valid printing commands: $view=0; $print=1; for (split(/\s+/,$batch)) { CASE: { /^q$/ and last CASE; # drop on n of q /^x[0-9]+$/ and last CASE; # x3 -> lpr -#3 /^b$/ and last CASE; # print a5 booklet /^t$/ and last CASE; # print twosided /^a$/ and last CASE; # print all /^(([0-9]+-?[0-9]*|[0-9]*-?[0-9]+),?)+$/ # n-m or n- or -m and last CASE; die "Illegal argument for -batch option: $batch\nUse q if you want no print\n"; } } } my $tonull=" >& ".devnull; my $fromnull=" < ".devnull; my $term=new Term::ReadLine 'vpp'; my $tmp=catfile(tmpdir,"$$"); # find the input: my $file=shift||""; my $in = new FileHandle; if ($file) { unless (-f $file) { my $n=0; my $ext=''; for (qw/ps pdf/) { -f "$file.$_" and $ext=$_,$n++; } CASE: { $n>1 and die "Found both $file.ps and $file.pdf: don't know which one to choose\n"; $n or die "Could not find $file, $file.pdf or $file.ps\n"; $file.=".$ext"; } } $in->open("< $file") or die "Could not open $file: $!\n"; } else { $in->fdopen(fileno(STDIN),'r'); } # find the input's type: my $res=''; $res.=$in->getc for (1..4); my $type=$res eq '%!PS' ? 'ps' : $res eq '%PDF' ? 'pdf' : ''; $type or die "This is not a PostScript of PDF file\n"; if ( $type eq 'ps' && ($viewer eq 'xpdf' || $viewer eq 'acroread')) { warn "pdf-viewer set for ps file! trying gv...\n" unless $quiet; $viewer = executable('gv',2); } # make a temporary pdf of the input: my $outcommand=($type eq 'ps' ? '|ps2pdf - ' : '')."> $tmp.pdf"; my $ou=new FileHandle $outcommand or die "Could not open: $outcommand: $!\n";; $ou->print($res); while ($_=$in->getline) { $ou->print($_); } $ou->close; # work in the temporary directory where the copy is: chdir tmpdir; $view ||$print or die "Nothing to do: use --view or --print or both\n"; $view and view($viewer,"$$.pdf"); $print and printout("$$.pdf",$batch); sub view { sys(@_); } sub printout { my $print_prompt="printer ready? then turn pack and type return "; while (1) { my ($selection,$booklet,$twosided,$lpropt)=ask_selection($batch); $selection eq 'quit' and quit(); $selection||=0; my $file="$$.pdf"; if ($selection eq 'continue') { next; } elsif ($selection) { # if anything was set sys("texexec --pdfselect --selection=$selection --result=$$"."a.pdf $$.pdf $tonull"); $file=$$."a.pdf"; } if ($booklet) { txc($file,1,'odd',$lpropt); $term->readline($print_prompt); txc($file,1,'even',$lpropt); } elsif ($twosided) { txc($file,0,'odd',$lpropt); $term->readline($print_prompt); txc($file,0,'even',$lpropt); } else { txc($file,0,'',$lpropt); } last if $batch; } } sub txc { # params: booklet: 1 or 0 # pages: odd even empty(=all) my ($file,$booklet,$oddeven,$lpropt)=@_; my $lpfile=$oddeven ? $$."eo.pdf" : $file; my $tx="texexec --result=$$"."eo $file $tonull"; my $opt=$booklet ? " --pdfarrange --paper=A5A4 --print=2UP --pages=" : " --pdfselect --selection="; $opt=$tx='' unless $oddeven; $oddeven.='< /dev/null && ' if $oddeven; if (sys("$tx$opt$oddeven lpr $lpropt $lpfile")) { warn "error: PDF probably contains unavailable fonts\n"; exit(1); } } sub ask_selection { my $prompt='vpp command (h for help): '; my $com=shift || ask($prompt); chomp($com); $com or return 'continue'; my ($booklet,$twosided,$selection,$lpropt)=('','','',''); for (split(/\s+/,$com)) { CASE: { /^q$/ and return 'quit'; # drop on q /^e$/ and exit 2; # edit request for caller /^v$/ and view($viewer,"$$.pdf"); # (re)view the ps/pdf data /^x[0-9]+$/ # x3 -> lpr -#3 and do { s/x//; $lpropt = '-#'.$_; }, last CASE; /^b$/ and $booklet=1, last CASE; # print a5 booklet /^t$/ and $twosided=1, last CASE; # print twosided /^a$/ and $selection='', last CASE; # print all /^(([0-9]+[:-]?[0-9]*|[0-9]*[:-]?[0-9]+),?)+$/ # n-m or n- or -m and do { s/:/-/g; # : is accepted for - s/^-/1-/; s/-$/-1000/; s/,$//; $selection=$_; }, last CASE; /^\?/ or warn "Illegal specification(s)\n"; warn "Examples of print commands:\n" . " 5 to print page 5\n" . " 5- to print pages 5 through the end\n" . " 5-7 to print pages 5, 6 and 7\n" . " -7 to print the first 7 pages\n" . " 5-7,19- to print pages 5, 6, 7 and 19 through the end\n" . " a to print the whole document\n" . " a x3 to print 3 copies of the document\n" . " x3 the same\n" . " 5 x3 to print 3 copies of page 5\n" . " t print the whole document twosided\n" . " t 2- print twosided starting at page 2\n" . " b to print the whole document as an a5 size booklet\n" . " b -12 to print the first 12 pages as an a5 size booklet\n" . "Other commands:\n" . " e (if called by mk) edit the tex source and rerun mk\n" . " v (re)view the ps/pdf file\n" . " h display this help\n" . " ? display this help\n" . " q quit\n" . ""; return 'continue'; } } $selection=~s/-/:/g; ($selection,$booklet,$twosided,$lpropt) } sub ask { my $x=$term->readline($_[0]); chomp($x); $x; } sub quit { sys("rm $$*; texutil --purge $tonull"); exit(0); } sub handle_options { ( my $myname = $0 ) =~ s/.*\///; # program name my ( $systemrcfile, $userrcfile, $rc_file ) = ( catfile( rootdir, 'etc', "${myname}rc" ), catfile( $ENV{HOME}, ".${myname}rc" ), ".${myname}rc" ); my @rcfiles = (); # here we'll remember which rc-files were read, so we can report # about them when $quiet appears to be false unless ( defined $ENV{NORC} ) { for ( $systemrcfile, $userrcfile, $rc_file ) { if ( -s $_ ) { push @rcfiles, $_; do $_; } } } GetOptions(@_) or help(); if ($rc) { -s $rc or die "Could not find rcfile $rc\n"; push @rcfiles, $rc; do $rc; } if ( @rcfiles && !$quiet ) { warn "The following rc-files were read: @rcfiles\n"; } } sub help { # help displays the contents of the NAME and SYNOPSIS pod sections # and then asks the user whether she wants to see the full man page open( IN, $0 ); while () { last if /=head1 NAME/; } while () { /=head1 SYNOPSIS/ and , next; last if /^=/; print; } if (executable("perldoc",2)) { $| = 1; @ARGV = (); # force further input from stdin print("\ntype m to see the man page or anything else to quit: "); chomp( my $in = <> ); exit 0 unless $in =~ /^m/; sys("perldoc $0"); exit 0; } } sub version { # version returns the CVS name and version information my $mess = "No version number assigned yet"; open IN, $0; while () { /\$Id: (.*),.*?(\d+\.\d+) (.*?) / and $mess = "$1 version $2 ($3)", last; } print "$mess\n"; exit 0; } sub executable { # executable return the full path of a named executable if it exists # if it does not, it returns undef, or it dies, depending on the second argument # arguments: # 1. name of an executable # 2. if 1, warn if executable is missing, if 2, make it lethal my ( $ex, $needed ) = @_; $needed||=0; $ex =~ s/\s+.*//; my $root = rootdir; for my $dir (path) { opendir( DOT, $dir =~ /^$root/ ? $dir : catfile(curdir,$dir) ) || next; while ( my $e = readdir(DOT) ) { next unless $e eq $ex; $e = catfile($dir,$e); next unless -f $e; next unless -x $e; return $e; } } $needed and warn "executable $ex needed but not found\n"; $needed > 1 and exit 1; return undef; } sub sys { $quiet or warn "system call: @_\n"; system("@_") and do { warn "vpp: error executing @_\n"; return 1; }; return 0; } =head1 HOW IT WORKS For the printing of B files, B makes use of B's B utility, except when you simply print one-sided and without scaling: in that case B just offers your B file to the print spooler. For twosided printing, B first selects the odd pages into a temporary B file and sends it to the print spooler, and then prompts the user for an key-press, selects the even pages and prints them: # odd pages: # even pages texexec --result=tmp.pdf \ texexec --result=tmp.pdf \ --pdfselect \ --pdfselect \ --selection=odd \ --selection=even \ input.pdf \ input.pdf lpr tmp.pdf lpr tmp.pdf For a booklet, B first lets B rearrange and scale the pages, and then prints both sides as before: texexec --result=tmp.pdf \ texexec --result=tmp.pdf \ --pdfarrange \ --pdfarrange \ --paper=A5A4 \ --paper=A5A4 \ --print=2UP \ --print=2UP \ --pages=odd \ --pages=even \ input.pdf input.pdf lpr tmp.pdf lpr tmp.pdf =head1 OPTIONS B comes with several options. The SYNOPSIS section shows an overview. Options are shown in logically identical pairs, with the full version in the first column and the minimum shorthand (without the parameters) in the second. Options marked with a star are boolean options. Default values are shown in the last column. You can set boolean options to false by prefixing the option with `no', for example: --noquiet or -noq. Before evaluating any options, B will try to read a system rc-file, a user rc-file, and, finally an rc-file in the current directory. The default values for -marked options and for string options can be set in these files. See the section `RC-files' for more information. You can also set option defaults in an alias. For example: alias vpp='vpp -noquiet' =over 8 =item --help Prints help information and lets you type `m' to display the complete man page or anything else to quit. =item --version Prints name and ( -)version and then quits. =item --quiet Suppresses messages about the progress B is making. This is the default. =item --rc Read specified rc-file before processing. The contents of the rc-file may override options specified before the --rc option, therefore it is a good idea to have the habit of specifying the --rc option first. =item --batch Prevents the --print option to interrogate the user about pages to be printed. Instead the document is printed according to the mandatory . Also sets viewing off. Thus the command vpp -batch '2-3 x3' test.pdf prints 3 copies of pages 2 and 3 of F, without viewing. =item --print Present the print prompt. This is the default. This option is normally used to suppress the print prompt, for example when using B from other scripts that generate B or B documents that have only to be displayed or printed without even being displayed. =item --view Run the file viewer. This is the default. This option is normally used to suppress starting the viewer, for example when using B from other scripts that generate B or B documents that have only to be printed. =back =head1 PAGE SELECTION When you select the --print option, and you did not also use the --batch option, B interrogates you about the pages you want to print. To that end the following prompt appears: vpp command (h for help): upon typing `h' B displays examples of possible commands: Examples of print commands: 5 to print page 5 5- to print pages 5 through the end 5-7 to print pages 5, 6 and 7 -7 to print the first 7 pages 5-7,19- to print pages 5, 6, 7 and 19 through the end a to print the whole document a x3 to print 3 copies of the document x3 the same 5 x3 to print 3 copies of page 5 t print the whole document twosided t 2- print twosided starting at page 2 b to print the whole document as an a5 size booklet b -12 to print the first 12 pages as an a5 size booklet Other commands: v re-view the ps/pdf file e signal to the caller (mk for example) to edit and recompile the source that generated the ps/pdf h display this help ? display this help q quit vpp command (h for help): With these examples, no further explanation should be necessary, except that, when twosided (`t') or booklet (`b') printing is selected, printing will be performed in two shifts, one for the front side and one for the backside. Between the shifts, another prompt appears: printer ready? then turn stack and type return You will have to arrange your printer such that, with the printed sides up, the first page printed will be at the bottom of the stack, and the last page printed will be on top. Normally you will then have your output come out the back of your printer. `Turn the stack' then means: rotate it over the long side of the paper and feed it back into the printer for the other side to be printed. =head1 RC-FILES AND CUSTOMIZATION Unless the environment variable NORC has been set, three rc-files are executed, if they exist, before reading the command line options, in the following order: =over 8 =item 1 /etc/vpprc: the system rc-file =item 2 $HOME/.vpprc: the user rc-file =item 3 ./.vpprc: the local rc-file =back You can use these rc-files to set the default values for the options, by setting the Perl variable named after the long version of the options. For example: $quiet=1; # run in quiet mode So if you usually like B to work quietly, you can indicate so in your rc-file and change your mind in some cases by using the --noquiet (or perhaps -noq) option. One other variable that can be set in the rc-files is: $viewer = 'gv'; # set the viewer B is the default viewer for both B and B. If you normally use B and have set, say, xpdf as your viewer, you would get in trouble when handling a B file. In such cases, therefore, B tries to switch to B. =head1 EXAMPLES Since B can read from standard input, it can be used to print (parts of) manpages: man -t ls - vpp If you don't need a preview, because you have seen the man page already, you can print it immediately as an A5 booklet with: man -t ls - vpp -batch=b For selection and scaling, ConTeXt needs to be run. If a document contains locally unavailable fonts, B will crash is such cases. =head1 AUTHOR Wybo Dekker C =head1 VERSION $Id: vpp,v 1.15 2002/11/12 09:34:06 wybo Exp $