ajcd@cs.edinburgh.ac.uk (Angus Duggan) (04/06/91)
People seem to have been asking for programs to perform page manipulation for PostScript recently; here's my set of utilities for selecting and rearranging pages from PostScript documents. #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of archive 1 (of 1)." # Contents: Makefile README epsffit.c psbook.1 psbook.c psnup # psselect.1 psselect.c pstops.1 pstops.c psutil.c psutil.h # Wrapped by ajcd@davaar on Sat Apr 6 12:36:26 1991 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f Makefile -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"Makefile\" else echo shar: Extracting \"Makefile\" \(643 characters\) sed "s/^X//" >Makefile <<'END_OF_Makefile' X# Makefile for PS utilities X XCFLAGS=-O X X# epsffit fits an epsf file to a given bounding box X# psbook rearranges pages into signatures X# psselect selects page ranges X# pstops performs general page rearrangement and merging X Xall: psbook psselect pstops epsffit X Xepsffit: epsffit.c X $(CC) $(CFLAGS) -o epsffit epsffit.c X Xpsbook: psbook.o psutil.o X $(CC) -o psbook psutil.o psbook.o X Xpsselect: psselect.o psutil.o X $(CC) -o psselect psutil.o psselect.o X Xpstops: pstops.o psutil.o X $(CC) -o pstops psutil.o pstops.o X Xpsbook.c: psutil.h X Xpstops.c: psutil.h X Xpsutil.c: psutil.h X Xpsselect.c: psutil.h X Xclean: X rm -f *.o psbook psselect pstops epsffit END_OF_Makefile if test 643 -ne `wc -c <Makefile`; then echo shar: \"Makefile\" unpacked with wrong size! fi # end of overwriting check fi if test -f README -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"README\" else echo shar: Extracting \"README\" \(1737 characters\) sed "s/^X//" >README <<'END_OF_README' XPostScript Utilities Angus Duggan 6 April 1991 X XThis shar file contains some utilities for manipulating PostScript documents. XPage selection and rearrangement are supported, including arrangement into Xsignatures for booklet printing, and page merging for 2up/4up/8up/9up printing. X XFILES X XThe files contained are: X X-rw-r--r-- 1 ajcd cs_pg 643 Apr 6 10:45 Makefile X-rw-r--r-- 1 ajcd cs_pg 1737 Apr 6 11:39 README X-rw-r--r-- 1 ajcd cs_pg 3673 Jan 29 17:28 epsffit.c X-rw-r--r-- 1 ajcd cs_pg 1066 Feb 12 13:18 psbook.1 X-rw-r--r-- 1 ajcd cs_pg 1990 Feb 7 18:54 psbook.c X-rwxr-xr-x 1 ajcd cs_pg 2121 Apr 6 12:00 psnup X-rw-r--r-- 1 ajcd cs_pg 1509 Feb 12 13:18 psselect.1 X-rw-r--r-- 1 ajcd cs_pg 4621 Feb 12 12:34 psselect.c X-rw-r--r-- 1 ajcd cs_pg 3227 Feb 20 10:16 pstops.1 X-rw-r--r-- 1 ajcd cs_pg 9272 Feb 20 14:06 pstops.c X-rw-r--r-- 1 ajcd cs_pg 5201 Feb 12 12:38 psutil.c X-rw-r--r-- 1 ajcd cs_pg 663 Feb 7 18:54 psutil.h X XPROGRAMS X Xpsbook rearranges pages into signatures Xpsselect selects pages and page ranges Xpstops performs general page rearrangement and selection Xpsnup uses pstops to merge multiple pages per sheet Xepsffit fits an EPSF file to a given bounding box X Xpsselect in modeled after Chris Torek's dviselect program, and psbook is Xmodeled after Tom Rokicki's dvidvi program. psbook is modeled on my own Xdvibook program, which borrows heavily from Chris Torek's dviselect. X XBUGS X XThe utilities don't check for PS-Adobe-?.? conformance; they assume documents Xconform. X XBug fixes and suggestions to ajcd@lfcs.edinburgh.ac.uk END_OF_README if test 1737 -ne `wc -c <README`; then echo shar: \"README\" unpacked with wrong size! fi # end of overwriting check fi if test -f epsffit.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"epsffit.c\" else echo shar: Extracting \"epsffit.c\" \(3673 characters\) sed "s/^X//" >epsffit.c <<'END_OF_epsffit.c' X/* epsffit.c X * AJCD 6 Dec 90 X * fit epsf file into constrained size X * Usage: X * epsffit [-c] [-r] [-a] [-s] llx lly urx ury X * -c centres the image in the bounding box given X * -r rotates the image by 90 degrees anti-clockwise X * -a alters the aspect ratio to fit the bounding box X * -s adds a showpage at the end of the image X */ X X#include <stdio.h> X#include <ctype.h> X X#define min(x,y) ((x) > (y) ? (y) : (x)) X#define max(x,y) ((x) > (y) ? (x) : (y)) X Xstatic char *prog; X Xusage() X{ X fprintf(stderr, "Usage: %s [-c] [-r] [-a] [-s] llx lly urx ury\n", prog); X exit(1); X} X Xmain(argc, argv) X int argc; X char **argv; X{ X int fit[4], i; X int bbfound = 0; /* %%BoundingBox: found */ X int urx, ury, llx, lly; X int furx, fury, fllx, flly, fwidth, fheight; X int showpage = 0, centre = 0, rotate = 0, aspect = 0; X char buf[BUFSIZ]; X X prog = *argv++; argc--; X X while (argc > 0 && argv[0][0] == '-') { X switch (argv[0][1]) { X case 'c': centre = 1; break; X case 's': showpage = 1; break; X case 'r': rotate = 1; break; X case 'a': aspect = 1; break; X default: usage(); X } X argc--; X argv++; X } X X if (argc != 4) usage(); X fllx = atoi(argv[0]); X flly = atoi(argv[1]); X furx = atoi(argv[2]); X fury = atoi(argv[3]); X if (rotate) { X fwidth = fury - flly; X fheight = furx - fllx; X } else { X fwidth = furx - fllx; X fheight = fury - flly; X } X X while (fgets(buf, BUFSIZ, stdin)) { X if (buf[0] == '%' && (buf[1] == '%' || buf[1] == '!')) { X /* still in comment section */ X if (!strncmp(buf, "%%BoundingBox:", 14)) { X if (sscanf(buf, "%%%%BoundingBox:%d %d %d %d\n", X &llx, &lly, &urx, &ury) == 4) X bbfound = 1; X } else if (!strncmp(buf, "%%EndComments", 13)) { X strcpy(buf, "\n"); /* don't repeat %%EndComments */ X break; X } else fputs(buf,stdout); X } else break; X } X if (bbfound) { /* put BB, followed by scale&translate */ X double width = urx-llx, height = ury-lly; X double xscale = fwidth/width, yscale = fheight/height; X double xoffset = fllx, yoffset = flly; X if (!aspect) { /* preserve aspect ratio ? */ X xscale = yscale = min(xscale,yscale); X } X width *= xscale; /* actual width and height after scaling */ X height *= yscale; X if (centre) { X if (rotate) { X xoffset += (fheight - height)/2; X yoffset += (fwidth - width)/2; X } else { X xoffset += (fwidth - width)/2; X yoffset += (fheight - height)/2; X } X } X printf("%%%%BoundingBox: %d %d %d %d\n", (int)xoffset, (int)yoffset, X (int)(xoffset+(rotate ? height : width)), X (int)(yoffset+(rotate ? width : height))); X if (rotate) { /* compensate for original image shift */ X xoffset += height + lly * yscale; /* displacement for rotation */ X yoffset -= llx * xscale; X } else { X xoffset -= llx * xscale; X yoffset -= lly * yscale; X } X puts("%%EndComments"); X if (showpage) X puts("save /showpage{}def /copypage{}def /erasepage{}def"); X else X puts("%%BeginProcSet: epsffit 1 0"); X puts("gsave"); X printf("%.3lf %.3lf translate\n", xoffset, yoffset); X if (rotate) X puts("90 rotate"); X printf("%.3lf %.3lf scale\n", xscale, yscale); X if (!showpage) X puts("%%EndProcSet"); X } X do { X fputs(buf,stdout); X } while (fgets(buf, BUFSIZ, stdin)); X if (bbfound) { X puts("grestore"); X if (showpage) X puts("restore showpage"); /* just in case */ X } else { X fprintf(stderr, "%s: no %%%%BoundingBox:\n", prog); X exit(1); X } X exit(0); X} END_OF_epsffit.c if test 3673 -ne `wc -c <epsffit.c`; then echo shar: \"epsffit.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f psbook.1 -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"psbook.1\" else echo shar: Extracting \"psbook.1\" \(1066 characters\) sed "s/^X//" >psbook.1 <<'END_OF_psbook.1' X.TH PSBOOK 1 X.SH NAME Xpsbook \- rearrange pages in PostScript file into signatures X.SH SYNOPSIS X.B psbook X[ X.B \-q X] [ X.B \-s\fIsignature\fR X] [ X.I infile X[ X.I outfile X] ] X.SH DESCRIPTION X.I Psbook Xrearranges pages from a PostScript document into ``signatures'' for Xprinting books or booklets, creating a new PostScript file. The Xinput PostScript file should follow the Adobe Document Structuring XConventions. X.PP XThe X.I \-s Xoption selects the size of signature which will be used. The signature size is Xthe number of sides which will be folded and bound together; the number given Xshould be a multiple of four. The default is to use one signature for the Xwhole file. Extra blank sides will be added if the file does not contain a Xmultiple of four pages. X.PP XPsbook normally prints the page numbers of the pages rearranged; the X.I \-q Xoption suppresses this. X.SH AUTHOR XAngus Duggan X.SH "SEE ALSO" Xpsselect(1), pstops(1) X.SH TRADEMARKS X.B PostScript Xis a trademark of Adobe Systems Incorporated. X.SH BUGS X.I Psbook Xcannot cope with documents longer than 5000 pages. END_OF_psbook.1 if test 1066 -ne `wc -c <psbook.1`; then echo shar: \"psbook.1\" unpacked with wrong size! fi # end of overwriting check fi if test -f psbook.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"psbook.c\" else echo shar: Extracting \"psbook.c\" \(1990 characters\) sed "s/^X//" >psbook.c <<'END_OF_psbook.c' X/* psbook.c X * AJCD 27/1/91 X * rearrange pages in conforming PS file for printing in signatures X * X * Usage: X * psbook [-q] [-s<signature>] [infile [outfile]] X */ X X#include "psutil.h" X Xvoid usage() X{ X fprintf(stderr, "Usage: %s [-q] [-s<signature>] [infile [outfile]]\n", X prog); X fprintf(stderr, " <signature> must be positive and divisible by 4\n"); X fflush(stderr); X exit(1); X} X X Xmain(argc, argv) X int argc; X char *argv[]; X{ X int signature = 0; X int currentpg, maxpage; X X infile = stdin; X outfile = stdout; X verbose = 1; X for (prog = *argv++; --argc; argv++) { X if (argv[0][0] == '-') { X switch (argv[0][1]) { X case 's': X signature = atoi(*argv+2); X if (signature < 1 || signature % 4) usage(); X break; X case 'q': X verbose = 0; X break; X default: X usage(); X } X } else if (infile == stdin) { X if ((infile = fopen(*argv, "r")) == NULL) { X fprintf(stderr, "%s: can't open input file %s\n", prog, *argv); X fflush(stderr); X exit(1); X } X } else if (outfile == stdout) { X if ((outfile = fopen(*argv, "w")) == NULL) { X fprintf(stderr, "%s: can't open output file %s\n", prog, *argv); X fflush(stderr); X exit(1); X } X } else usage(); X } X if ((infile=seekable(infile))==NULL) { X fprintf(stderr, "%s: can't seek input\n", prog); X fflush(stderr); X exit(1); X } X scanpages(); X X maxpage = pages+(4-pages%4)%4; X X if (!signature) X signature = maxpage; X X /* rearrange pages */ X writeheader(maxpage); X writeprolog(); X for (currentpg = 0; currentpg < maxpage; currentpg++) { X int actualpg = currentpg - currentpg%signature; X switch(currentpg%4) { X case 0: X case 3: X actualpg += signature-1-(currentpg%signature)/2; X break; X case 1: X case 2: X actualpg += (currentpg%signature)/2; X break; X } X if (actualpg < pages) X writepage(actualpg); X else X writeemptypage(); X } X writetrailer(); X X exit(0); X} END_OF_psbook.c if test 1990 -ne `wc -c <psbook.c`; then echo shar: \"psbook.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f psnup -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"psnup\" else echo shar: Extracting \"psnup\" \(2121 characters\) sed "s/^X//" >psnup <<'END_OF_psnup' X#!/bin/sh X# psnup: put multiple pages onto one physical sheet of paper. X# usage: X# psnup [-w<dim>] [-h<dim>] [-l] [-2|-4|-8|-9] [file...] X# -w<dim> sets the paper width X# -h<dim> sets the paper height X# -l is used if the pages are in landscape orientation X Xio= landscape=0 nup=1 width=-w21cm height=-h29.7cm X Xwhile test $# != 0 Xdo case "$1" in X -w*) width=$1 ;; X -h*) height=$1 ;; X -l) landscape=1 ;; X -2) nup=2 ;; X -4) nup=4 ;; X -8) nup=8 ;; X -9) nup=9 ;; X *) io="$io $1" X esac X shift Xdone X Xscale= offset= Xcase "$nup" in X2) scale=@0.707 X if [ $landscape = 0 ] X then offset="(1w,0) (1w,0.5h)" X else offset="(0,0.5h) (0,0)" X fi X landscape=`expr 1 - $landscape` ;; X4) scale=@0.5 X if [ $landscape = 0 ] X then offset="(0,0.5h) (0.5w,0.5h) (0,0) (0.5w,0)" X else offset="(0.5w,0) (0.5w,0.5h) (1w,0) (1w,0.5h)" X fi ;; X8) scale=@0.3536 X if [ $landscape = 0 ] X then offset="(0.5w,0) (0.5w,0.25h) (0.5w,0.5h) (0.5w,0.75h)\ X (1w,0) (1w,0.25h) (1w,0.5h) (1w,0.75h)" X else offset="(0,0.75h) (0.5w,0.75h) (0,0.5h) (0.5w,0.5h)\ X (0,0.25h) (0.5w,0.25h) (0,0.25h) (0.5w,0.25h)" X fi X landscape=`expr 1 - $landscape` ;; X9) scale=@0.3333 X if [ $landscape = 0 ] X then offset="(0,0.666h) (0.333w,0.666h) (0.666w,0.666h)\ X (0,0.333h) (0.333w,0.333h) (0.666w,0.333h)\ X (0,0) (0.333w,0) (0.666w,0)" X else offset="(0.333w,0) (0.333w,0.333h) (0.333w,0.666h)\ X (0.666w,0) (0.666w,0.333h) (0.666w,0.666h)\ X (1w,0) (1w,0.333h) (1w,0.666h)" X fi ;; Xesac X Xif [ $landscape = 0 ] Xthen rotate= Xelse rotate=L Xfi X Xoptions= sep= page=0 X Xset -- ${offset:-""} Xwhile [ $page -lt $nup ] Xdo options="$options${options:++}$page$rotate$scale$1" X page=`expr $page + 1` X shift Xdone X Xpstops $width $height "$nup:$options" $io END_OF_psnup if test 2121 -ne `wc -c <psnup`; then echo shar: \"psnup\" unpacked with wrong size! fi chmod +x psnup # end of overwriting check fi if test -f psselect.1 -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"psselect.1\" else echo shar: Extracting \"psselect.1\" \(1509 characters\) sed "s/^X//" >psselect.1 <<'END_OF_psselect.1' X.TH PSSELECT 1 X.SH NAME Xpsselect \- select pages from a PostScript file X.SH SYNOPSIS X.B psselect X[ X.B \-q X] [ X.B \-e X] [ X.B \-o X] [ X.B \-r X] [ X.B \-p\fIpages\fR X] [ X.I infile X[ X.I outfile X] ] X.SH DESCRIPTION X.I Psselect Xselects pages from a PostScript document, creating a new PostScript file. The Xinput PostScript file should follow the Adobe Document Structuring XConventions. X.PP XThe X.I \-e Xoption selects all of the even pages; it may be used in conjunction with the Xother page selection options. X.PP XThe X.I \-o Xoption selects all of the odd pages; it may be used in conjunction with the Xother page selection options. X.PP XThe X.I \-p\fIpages\fR Xoption specifies the pages which are to be selected. X.I Pages Xis a comma separated list of page ranges, each of which may be a page number, Xor a page range of the form \fIfirst\fR-\fIlast\fR. If \fIfirst\fR is omitted, Xthe Xfirst page is assumed, and if \fIlast\fR is omitted, the last page is assumed. X.PP XThe X.I \-r Xoption causes X.I psselect Xto output the selected pages in reverse order. X.PP XPsselect normally prints the page numbers of the pages rearranged; the X.I \-q Xoption suppresses this. X.SH NOTES X.I Psselect Xselects pages in the order that they appear in the file, starting from one. The Xactual page number in the document may be different. X.SH AUTHOR XAngus Duggan X.SH "SEE ALSO" Xpsbook(1), pstops(1) X.SH TRADEMARKS X.B PostScript Xis a trademark of Adobe Systems Incorporated. X.SH BUGS X.I Psselect Xcannot cope with documents longer than 5000 pages. END_OF_psselect.1 if test 1509 -ne `wc -c <psselect.1`; then echo shar: \"psselect.1\" unpacked with wrong size! fi # end of overwriting check fi if test -f psselect.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"psselect.c\" else echo shar: Extracting \"psselect.c\" \(4621 characters\) sed "s/^X//" >psselect.c <<'END_OF_psselect.c' X/* psselect.c X * AJCD 27/1/91 X * rearrange pages in conforming PS file for printing in signatures X * X * Usage: X * psselect [-q] [-e] [-o] [-r] [-p<pages>] [infile [outfile]] X */ X X#include "psutil.h" X Xvoid usage() X{ X fprintf(stderr, X "Usage: %s [-q] [-e] [-o] [-r] [-p<pages>] [infile [outfile]]\n", X prog); X fflush(stderr); X exit(1); X} X Xstruct pgrange { X int first, last; X struct pgrange *next; X}; X Xtypedef struct pgrange range; X Xrange * makerange(beg, end, next) X int beg, end; X range *next; X{ X range *new; X if ((new = (range *)malloc(sizeof(range))) == NULL) { X fprintf(stderr, "%s: out of memory\n", prog); X fflush(stderr); X exit(1); X } X new->first = beg; X new->last = end; X new->next = next; X return (new); X} X Xrange * mergerange(beg, end, curr) X int beg, end; X range *curr; X{ X if (curr) { X range *this, *prev; X for (this=prev=curr; this; prev=this, this=this->next) { X int lo = (beg < curr->first) ? -1 : (beg > curr->last) ? 1 : 0; X int hi = (end < curr->first) ? -1 : (end > curr->last) ? 1 : 0; X if (hi < 0) X return (makerange(beg, end, curr)); X else if (lo <= 0) { /* beginning of range */ X if (lo < 0) X this->first = beg; X if (hi > 0) { X while (this->next && this->next->first <= end) { X range *eaten = this->next; X this->next = eaten->next; X if (eaten->last > end) X end = eaten->last; X free(eaten); X } X this->last = end; X } /* else range included; no change */ X return (curr); X } X } X prev->next = makerange(beg, end, NULL); X return (curr); X } else return (makerange(beg, end, NULL)); X} X X Xrange * addrange(str, rp) X char *str; X range *rp; X{ X int first=0; X if (isdigit(*str)) { X first = atoi(str); X while (isdigit(*str)) str++; X } X switch (*str) { X case '\0': X if (first) X return (mergerange(first, first, rp)); X break; X case ',': X if (first) X return (addrange(str+1, mergerange(first, first, rp))); X break; X case '-': X case ':': X str++; X if (isdigit(*str)) { X int last = atoi(str); X while (isdigit(*str)) str++; X if (!first) X first = 1; X if (last >= first) X switch (*str) { X case '\0': X return (mergerange(first, last, rp)); X case ',': X return (addrange(str+1, mergerange(first, last, rp))); X } X } else if (*str == '\0') X return (mergerange(first, MAXPAGES, rp)); X } X fprintf(stderr, "%s: invalid page range\n", prog); X fflush(stderr); X exit(1); X} X X Xint selectpage(page, even, odd, rp) X int page, even, odd; X range *rp; X{ X if (page&1) { X if (odd) return (1); X } else { X if (even) return (1); X } X while (rp) { X if (page >= rp->first) { X if (page <= rp->last) X return (1); X } else return (0); X rp = rp->next; X } X return (0); X} X X Xmain(argc, argv) X int argc; X char *argv[]; X{ X int currentpg, maxpage = 0; X int even = 0, odd = 0, all = 1, reverse = 0; X range *pagerange = NULL; X X infile = stdin; X outfile = stdout; X verbose = 1; X for (prog = *argv++; --argc; argv++) { X if (argv[0][0] == '-') { X switch (argv[0][1]) { X case 'e': X even = 1; all = 0; X break; X case 'o': X odd = 1; all = 0; X break; X case 'r': X reverse = 1; X break; X case 'p': X pagerange = addrange(*argv+2, pagerange); all = 0; X break; X case 'q': X verbose = 0; X break; X default: X usage(); X } X } else if (infile == stdin) { X if ((infile = fopen(*argv, "r")) == NULL) { X fprintf(stderr, "%s: can't open input file %s\n", prog, *argv); X fflush(stderr); X exit(1); X } X } else if (outfile == stdout) { X if ((outfile = fopen(*argv, "w")) == NULL) { X fprintf(stderr, "%s: can't open output file %s\n", prog, *argv); X fflush(stderr); X exit(1); X } X } else usage(); X } X if ((infile=seekable(infile))==NULL) { X fprintf(stderr, "%s: can't seek input\n", prog); X fflush(stderr); X exit(1); X } X scanpages(); X X for (currentpg = 1; currentpg <= pages; currentpg++) X if (selectpage(currentpg, even || all, odd || all, pagerange)) X maxpage++; X X /* select pages */ X writeheader(maxpage); X writeprolog(); X if (reverse) { X for (currentpg = pages; currentpg > 0; currentpg--) X if (selectpage(currentpg, even || all, odd || all, pagerange)) X writepage(currentpg-1); X } else { X for (currentpg = 1; currentpg <= pages; currentpg++) X if (selectpage(currentpg, even || all, odd || all, pagerange)) X writepage(currentpg-1); X } X writetrailer(); X X exit(0); X} END_OF_psselect.c if test 4621 -ne `wc -c <psselect.c`; then echo shar: \"psselect.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f pstops.1 -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"pstops.1\" else echo shar: Extracting \"pstops.1\" \(3227 characters\) sed "s/^X//" >pstops.1 <<'END_OF_pstops.1' X.TH PSTOPS 1 X.SH NAME Xpstops \- select pages from a PostScript file X.SH SYNOPSIS X.B pstops X[ X.B \-q X] X[ X.B \-b X] X[ X.B \-w\fIwidth\fR X] X[ X.B \-h\fIheight\fR X] X.I pagespecs X[ X.I infile X[ X.I outfile X] ] X.SH DESCRIPTION X.I Pstops Xrearranges pages from a PostScript document, creating a new PostScript file. XThe input PostScript file should follow the Adobe Document Structuring XConventions. X.I Pstops Xcan be used to perform a large number of arbitrary re-arrangements of XDocuments, including arranging for printing 2-up, 4-up, booklets, reversing, Xselecting front or back sides of documents, scaling, etc. X.PP X.I pagespecs Xfollow the syntax: X.RS X.TP 12 X.I pagespecs X.I = [modulo:]specs X.TP X.I specs X.I = spec[+specs][,specs] X.TP X.I spec X.I = [-]pageno[@scale][L][R][U][(xoff,yoff)] X.RE X.sp X.I modulo Xis the number of pages in each block. The value of X.I modulo Xshould be greater than 0; the default value is 1. X.I specs Xare the page specifications for the pages in each block. The value of the X.I pageno Xin each X.I spec Xshould be between 0 (for the first page in the block) and \fImodulo\fR-1 X(for the last page in each block) inclusive. XThe optional dimensions X.I xoff Xand X.I yoff Xshift the page by the specified (positive) amount. X.I xoff Xand X.I yoff Xare in PostScript's points, but may be followed by the units X.B "cm" Xor X.B "in" Xto convert to centimetres or inches, or the flag X.B "w" Xor X.B "h" Xto specify as a multiple of the width or height. XThe optional parameters \fIL\fR, \fIR\fR, and \fIU\fR rotate the page left, Xright, or upside-down. XThe optional X.I scale Xparameter scales the page by the fraction specified. XIf the optional minus sign is specified, the page is relative to the end of Xthe document, instead of the start. X XIf page \fIspec\fRs are separated by X.B \+ Xthe pages will be merged into one page; if they are separated by X.B \, Xthey will be on separate pages. XIf there is only one page specification, with X.I pageno Xzero, the \fIpageno\fR may be omitted. X.PP XThe X.I \-w Xoption gives the width which is used by the X.B "w" Xdimension specifier, and the X.I \-h Xoption gives the height which is used by the X.B "h" Xdimension specifier. These dimensions are also used (after scaling) to set the Xclipping path for each page. X.PP XThe X.I \-b Xoption prevents any X.B bind Xoperators in the PostScript prolog from binding. This may be needed in cases Xwhere complex multi-page re-arrangements are being done. X.PP XPstops normally prints the page numbers of the pages re-arranged; the X.I \-q Xoption suppresses this. X.SH EXAMPLES XThis section contains some sample re-arrangements. To put two pages on one Xsheet (of A4 paper), the pagespec to use is: X.sp X.ce X2:0L@0.7(21cm,0)+1L@0.7(21cm,14.85cm) X.sp XTo select all of the odd pages in reverse order, use: X.sp X.ce X2:-0 X.sp XTo re-arrange pages for printing 2-up booklets, use X.sp X.ce X4:-3L@0.7(21cm,0)+0L@0.7(21cm,14.85cm) X.sp Xfor the front sides, and X.sp X.ce X4:1L@0.7(21cm,0)+-2L@0.7(21cm,14.85cm) X.sp Xfor the reverse sides (or join them with a comma for duplex printing). X.SH AUTHOR XAngus Duggan X.SH "SEE ALSO" Xpsbook(1), psselect(1) X.SH TRADEMARKS X.B PostScript Xis a trademark of Adobe Systems Incorporated. X.SH BUGS X.I Pstops Xcannot cope with documents longer than 5000 pages. END_OF_pstops.1 if test 3227 -ne `wc -c <pstops.1`; then echo shar: \"pstops.1\" unpacked with wrong size! fi # end of overwriting check fi if test -f pstops.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"pstops.c\" else echo shar: Extracting \"pstops.c\" \(9272 characters\) sed "s/^X//" >pstops.c <<'END_OF_pstops.c' X/* pstops.c X * AJCD 27/1/91 X * rearrange pages in conforming PS file for printing in signatures X * X * Usage: X * pstops [-q] [-b] [-w<dim>] [-h<dim>] <pagespecs> [infile [outfile]] X */ X X#include "psutil.h" X Xvoid usage() X{ X fprintf(stderr, "Usage: %s [-q] [-b] [-w<dim>] [-h<dim] <pagespecs> [infile [outfile]]\n", X prog); X fflush(stderr); X exit(1); X} X Xvoid specusage() X{ X fprintf(stderr, "%s: page specification error:\n", prog); X fprintf(stderr, " <pagespecs> = [modulo:]<spec>\n"); X fprintf(stderr, " <spec> = [-]pageno[@scale][L|R|U][(xoff,yoff)][,spec|+spec]\n"); X fprintf(stderr, " modulo>=1, 0<=pageno<modulo\n"); X fflush(stderr); X exit(1); X} X Xstatic int modulo = 1; Xstatic int pagesperspec = 1; Xstatic double width = -1.0; Xstatic double height = -1.0; X X/* pagespec flags */ X#define ADD_NEXT (0x01) X#define ROTATE (0x02) X#define SCALE (0x04) X#define OFFSET (0x08) X#define GSAVE (ROTATE|SCALE|OFFSET) X Xstruct pagespec { X int reversed, pageno, flags, rotate; X double xoff, yoff, scale; X struct pagespec *next; X}; X Xstruct pagespec *newspec() X{ X struct pagespec *temp = (struct pagespec *)malloc(sizeof(struct pagespec)); X temp->reversed = temp->pageno = temp->flags = temp->rotate = 0; X temp->scale = 1.0; X temp->xoff = temp->yoff = 0.0; X temp->next = NULL; X return (temp); X} X Xint parseint(sp) X char **sp; X{ X char *s; X int n = 0; X X for (s = *sp; isdigit(*s); s++) X n = n*10 + (*s-'0'); X if (*sp == s) specusage(); X *sp = s; X return (n); X} X Xdouble parsedouble(sp) X char **sp; X{ X int n = parseint(sp); X char *s = *sp; X int d = 0, frac = 1; X X if (*s == '.') { X *sp = ++s; X for (; isdigit(*s); s++) { X d = d*10 + (*s-'0'); X frac *= 10; X } X if (*sp == s) specusage(); X } X *sp = s; X return ((double)n+(double)d/frac); X} X Xdouble parsedimen(sp) X char **sp; X{ X double num = parsedouble(sp); X char *s = *sp; X X if (strncmp(s, "pt", 2) == 0) { X s += 2; X } else if (strncmp(s, "in", 2) == 0) { X num *= 72.0; X s += 2; X } else if (strncmp(s, "cm", 2) == 0) { X num *= 28.346456692913385211; X s += 2; X } else if (*s == 'w') { X if (width < 0.0) { X fprintf(stderr, "%s: width not initialised\n", prog); X fflush(stderr); X exit(1); X } X num *= width; X s++; X } else if (*s == 'h') { X if (height < 0.0) { X fprintf(stderr, "%s: height not initialised\n", prog); X fflush(stderr); X exit(1); X } X num *= height; X s++; X } X *sp = s; X return (num); X} X Xstruct pagespec *parsespecs(str) X char *str; X{ X char *t; X struct pagespec *head, *tail; X int other = 0; X int num = -1; X X head = tail = newspec(); X while (*str) { X if (isdigit(*str)) { X num = parseint(&str); X } else { X switch (*str++) { X case ':': X if (other || head != tail || num < 1) specusage(); X modulo = num; X num = -1; X break; X case '-': X tail->reversed = !tail->reversed; X break; X case '@': X if (num < 0) specusage(); X tail->scale *= parsedouble(&str); X tail->flags |= SCALE; X break; X case 'l': case 'L': X tail->rotate += 90; X tail->flags |= ROTATE; X break; X case 'r': case 'R': X tail->rotate -= 90; X tail->flags |= ROTATE; X break; X case 'u': case 'U': X tail->rotate += 180; X tail->flags |= ROTATE; X break; X case '(': X tail->xoff += parsedimen(&str); X if (*str++ != ',') specusage(); X tail->yoff += parsedimen(&str); X if (*str++ != ')') specusage(); X tail->flags |= OFFSET; X break; X case '+': X tail->flags |= ADD_NEXT; X case ',': X if (num < 0 || num >= modulo) specusage(); X if ((tail->flags & ADD_NEXT) == 0) X pagesperspec++; X tail->pageno = num; X tail->next = newspec(); X tail = tail->next; X num = -1; X break; X default: X specusage(); X } X other = 1; X } X } X if (num >= modulo) X specusage(); X else if (num >= 0) X tail->pageno = num; X return (head); X} X Xdouble singledimen(str) X char *str; X{ X double num = parsedimen(&str); X if (*str) usage(); X return (num); X} X X Xmain(argc, argv) X int argc; X char *argv[]; X{ X int thispg, maxpage; X int pageindex = 0; X struct pagespec *specs = NULL; X int nobinding = 0; X X infile = stdin; X outfile = stdout; X verbose = 1; X for (prog = *argv++; --argc; argv++) { X if (argv[0][0] == '-') { X switch (argv[0][1]) { X case 'q': X verbose = 0; X break; X case 'b': X nobinding = 1; X break; X case 'w': X width = singledimen(*argv+2); X break; X case 'h': X height = singledimen(*argv+2); X break; X default: X if (specs == NULL) X specs = parsespecs(*argv); X else X usage(); X } X } else if (specs == NULL) X specs = parsespecs(*argv); X else if (infile == stdin) { X if ((infile = fopen(*argv, "r")) == NULL) { X fprintf(stderr, "%s: can't open input file %s\n", prog, *argv); X fflush(stderr); X exit(1); X } X } else if (outfile == stdout) { X if ((outfile = fopen(*argv, "w")) == NULL) { X fprintf(stderr, "%s: can't open output file %s\n", prog, *argv); X fflush(stderr); X exit(1); X } X } else usage(); X } X if (specs == NULL) X usage(); X if ((infile=seekable(infile))==NULL) { X fprintf(stderr, "%s: can't seek input\n", prog); X fflush(stderr); X exit(1); X } X scanpages(); X X maxpage = ((pages+modulo-1)/modulo)*modulo; X X /* rearrange pages: doesn't cope properly with: X * initmatrix, initgraphics, defaultmatrix, grestoreall, initclip */ X writeheader((maxpage/modulo)*pagesperspec); X writestring("%%BeginProcSet: pstops 1 0\n"); X writestring("[/showpage/erasepage/copypage]{dup where{pop dup load\n"); X writestring(" type/operatortype eq{1 array cvx dup 0 3 index cvx put\n"); X writestring(" bind def}{pop}ifelse}{pop}ifelse}forall\n"); X writestring("[/letter/legal/executivepage/a4/a4small/b5/com10envelope\n"); X writestring(" /monarchenvelope/c5envelope/dlenvelope/lettersmall/note\n"); X writestring(" /folio/quarto/a5]{dup where{exch{}put}{pop}ifelse}forall\n"); X writestring("/lcvx{dup load dup type dup/operatortype eq{pop exch pop}\n"); X writestring(" {/arraytype eq{dup xcheck{exch pop aload pop}\n"); X writestring(" {pop cvx}ifelse}{pop cvx}ifelse}ifelse}bind def\n"); X writestring("/pstopsmatrix matrix currentmatrix def\n"); X writestring("/defaultmatrix{pstopsmatrix exch copy}bind def\n"); X writestring("/initmatrix{matrix defaultmatrix setmatrix}bind def\n"); X writestring("/pathtoproc{[{currentpoint}stopped{$error/newerror false\n"); X writestring(" put{newpath}}{/newpath cvx 3 1 roll/moveto cvx 4 array\n"); X writestring(" astore cvx}ifelse]{[/newpath cvx{/moveto cvx}{/lineto cvx}\n"); X writestring(" {/curveto cvx}{/closepath cvx}pathforall]cvx exch pop}\n"); X writestring(" stopped{$error/errorname get/invalidaccess eq{cleartomark\n"); X writestring(" $error/newerror false put cvx exec}{stop}ifelse}if}def\n"); X if (width > 0.0 && height > 0.0) { X char buffer[BUFSIZ]; X writestring("/initclip[/pathtoproc lcvx/matrix lcvx/currentmatrix lcvx"); X writestring("/initmatrix lcvx/initclip lcvx /newpath lcvx\n"); X writestring(" 0 0 /moveto lcvx\n"); X sprintf(buffer, X " %lf 0/rlineto lcvx 0 %lf/rlineto lcvx -%lf 0/rlineto lcvx\n", X width, height, width); X writestring(buffer); X writestring(" /clip lcvx /newpath lcvx /setmatrix lcvx /exec lcvx]\n"); X writestring(" cvx def\n"); X } X writestring("/initgraphics{initmatrix newpath initclip 1 setlinewidth\n"); X writestring(" 0 setlinecap 0 setlinejoin []0 setdash 0 setgray\n"); X writestring(" 0 setmiterlimit}bind def\n"); X if (nobinding) /* desperation measures */ X writestring("/bind{}def\n"); X writestring("%%EndProcSet\n"); X writeprolog(); X for (thispg = 0; thispg < maxpage; thispg += modulo) { X int ppp, add_last = 0; X struct pagespec *ps; X for (ppp = 0, ps = specs; ps != NULL; ppp++, ps = ps->next) { X int actualpg; X int add_next = ((ps->flags & ADD_NEXT) != 0); X if (ps->reversed) X actualpg = maxpage-thispg-modulo+ps->pageno; X else X actualpg = thispg+ps->pageno; X if (actualpg < pages) X seekpage(actualpg); X if (!add_last) { X writepageheader("pstops", ++pageindex); X } X writestring("gsave\n"); X if (ps->flags & GSAVE) { X char buffer[BUFSIZ]; X if (ps->flags & OFFSET) { X sprintf(buffer, "%lf %lf translate\n", ps->xoff, ps->yoff); X writestring(buffer); X } X if (ps->flags & ROTATE) { X sprintf(buffer, "%d rotate\n", ps->rotate); X writestring(buffer); X } X if (ps->flags & SCALE) { X sprintf(buffer, "%lf dup scale\n", ps->scale); X writestring(buffer); X } X writestring("/pstopsmatrix matrix currentmatrix def\n"); X } X if (width > 0.0 && height > 0.0) { X writestring("initclip\n"); X } X writestring("/pstopssaved save def\n"); X if (add_next) { X writestring("/showpage{}def/copypage{}def/erasepage{}def\n"); X } X if (actualpg < pages) X writepagebody(); X else X writestring("showpage\n"); X writestring("pstopssaved restore grestore\n"); X add_last = add_next; X } X } X writetrailer(); X X exit(0); X} END_OF_pstops.c if test 9272 -ne `wc -c <pstops.c`; then echo shar: \"pstops.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f psutil.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"psutil.c\" else echo shar: Extracting \"psutil.c\" \(5201 characters\) sed "s/^X//" >psutil.c <<'END_OF_psutil.c' X/* psutil.c X * AJCD 29/1/91 X * utilities for PS programs X */ X X#define LOCAL X#include "psutil.h" X X#include <fcntl.h> X#include <string.h> X Xstatic char buffer[BUFSIZ]; Xstatic long bytes = 0; Xstatic long pagescmt = 0; Xstatic long headerlen = 0; Xstatic int outputpage = 0; Xstatic char pagelabel[BUFSIZ]; Xstatic int pageno; Xstatic long pagelength; X X/* make a file seekable; trick stolen from Chris Torek's libdvi */ X XFILE *seekable(fp) X FILE *fp; X{ X int fd, tf, n, w; X char *tmpdir, *p; X X fd = fileno(fp); X if (lseek(fd, 0L, 1) >= 0 && !isatty(fd)) X return (fp); X X if ((tmpdir = getenv("TMPDIR")) == NULL) X tmpdir = TMPDIR; X (void) sprintf(buffer, "%s/#%d", tmpdir, getpid()); X if ((tf = open(buffer, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1) X return (NULL); X (void) unlink(buffer); X X while ((n = read(fd, p = buffer, BUFSIZ)) > 0) { X do { X if ((w = write(tf, p, n)) < 0) { X (void) close(tf); X (void) fclose(fp); X return (NULL); X } X p += w; X } while ((n -= w) > 0); X } X if (n < 0) { X (void) close(tf); X (void) fclose(fp); X return (NULL); X } X X /* discard the input file, and rewind and open the temporary */ X (void) fclose(fp); X (void) lseek(tf, 0L, 0); X if ((fp = fdopen(tf, "r")) == NULL) { X (void) close(tf); X } X return (fp); X} X X Xint fcopy(len) X long len; X{ X while (len) { X int n = (len > BUFSIZ) ? BUFSIZ : len; X if (!(fread(buffer, sizeof(char), n, infile) && X fwrite(buffer, sizeof(char), n, outfile))) X return (0); X len -= n; X bytes += n; X } X return (1); X} X X/* build array of pointers to start/end of pages */ X Xscanpages() X{ X register char *comment = buffer+2; X register int nesting = 0; X X pages = 0; X fseek(infile, 0L, 0); X while (fgets(buffer, BUFSIZ, infile) != NULL) X if (*buffer == '%') { X if (buffer[1] == '%') { X if (strncmp(comment, "Page:", 5) == 0) X pageptr[pages++] = ftell(infile)-strlen(buffer); X else if (headerlen == 0 && strncmp(comment, "Pages:", 6) == 0) X pagescmt = ftell(infile)-strlen(buffer); X else if (headerlen == 0 && X strncmp(comment, "EndComments", 11) == 0) X headerlen = ftell(infile); X else if (strncmp(comment, "BeginDocument", 13) == 0) X nesting++; X else if (strncmp(comment, "EndDocument", 11) == 0) X nesting--; X else if (strncmp(comment, "BeginBinary", 11) == 0) X nesting++; X else if (strncmp(comment, "EndBinary", 9) == 0) X nesting--; X else if (nesting == 0 && strncmp(comment, "Trailer", 7) == 0) { X fseek(infile, (long)(-strlen(buffer)), 1); X break; X } X } else if (headerlen == 0 && buffer[1] != '!') X headerlen = ftell(infile)-strlen(buffer); X } X pageptr[pages] = ftell(infile); X} X Xseekpage(p) X int p; X{ X fseek(infile, pageptr[p], 0); X if (fgets(buffer, BUFSIZ, infile) != NULL && X sscanf(buffer, "%%%%Page: %s %d\n", pagelabel, &pageno) == 2) { X pagelength = pageptr[p+1]-pageptr[p]-strlen(buffer); X } else { X fprintf(stderr, "%s: I/O error seeking page %d\n", prog, p); X fflush(stderr); X exit(1); X } X} X Xwritestring(s) X char *s; X{ X fputs(s, outfile); X bytes += strlen(s); X} X Xwritepageheader(label, page) X char *label; X int page; X{ X if (verbose) { X sprintf(buffer, "[%d] ", page); X message(buffer); X } X sprintf(buffer, "%%%%Page: %s %d\n", label, ++outputpage); X writestring(buffer); X} X Xwritepagebody() X{ X if (!fcopy(pagelength)) { X fprintf(stderr, "%s: I/O error writing page %d\n", prog, outputpage); X fflush(stderr); X exit(1); X } X} X Xwritepage(p) X int p; X{ X seekpage(p); X writepageheader(pagelabel, p+1); X writepagebody(); X} X X/* write header: should alter %%Pages: comment */ Xwriteheader(p) X int p; X{ X long len = headerlen; X fseek(infile, 0L, 0); X if (pagescmt && pagescmt < len) { X if (!fcopy(pagescmt) || fgets(buffer, BUFSIZ, infile) == NULL) { X fprintf(stderr, "%s: I/O error in header\n", prog); X fflush(stderr); X exit(1); X } X len -= pagescmt+strlen(buffer); X sprintf(buffer, "%%%%Pages: %d 0\n", p); X writestring(buffer); X } X if (!fcopy(len)) { X fprintf(stderr, "%s: I/O error in header\n", prog); X fflush(stderr); X exit(1); X } X} X X Xwriteprolog() X{ X if (!fcopy(pageptr[0]-headerlen)) { X fprintf(stderr, "%s: I/O error in prologue\n", prog); X fflush(stderr); X exit(1); X } X} X X/* write trailer */ Xwritetrailer() X{ X fseek(infile, pageptr[pages], 0); X while (fgets(buffer, BUFSIZ, infile) != NULL) { X writestring(buffer); X } X if (verbose) { X sprintf(buffer, "Wrote %d pages, %ld bytes\n", outputpage, bytes); X message(buffer); X } X} X X/* write message to stderr */ Xmessage(s) X char *s; X{ X static int pos = 0; X char *nl = strchr(s, '\n'); X int len = nl ? (nl-s) : strlen(s); X X if (pos+len > 79 && (pos > 79 || len < 80)) { X fputc('\n', stderr); X pos = 0; X } X fputs(s, stderr); X fflush(stderr); X pos += len; X} X X Xint writeemptypage() X{ X if (verbose) X message("[*] "); X sprintf(buffer, "%%%%Page: * %d\nshowpage\n", ++outputpage); X writestring(buffer); X} X END_OF_psutil.c if test 5201 -ne `wc -c <psutil.c`; then echo shar: \"psutil.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f psutil.h -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"psutil.h\" else echo shar: Extracting \"psutil.h\" \(663 characters\) sed "s/^X//" >psutil.h <<'END_OF_psutil.h' X/* psutil.h X * AJCD 29/1/91 X * utilities for PS programs X */ X X#include <stdio.h> X X#ifndef LOCAL X#define LOCAL extern X#endif X X#define TMPDIR "/tmp" X#define MAXPAGES 5000 /* max pages in document */ X XLOCAL char *prog; XLOCAL long pageptr[MAXPAGES]; XLOCAL int pages; XLOCAL int verbose; XLOCAL FILE *infile; XLOCAL FILE *outfile; X XLOCAL FILE *seekable(); XLOCAL int fcopy(); XLOCAL writepage(); XLOCAL seekpage(); XLOCAL writepageheader(); XLOCAL writepagebody(); XLOCAL writeheader(); XLOCAL writeprolog(); XLOCAL writetrailer(); XLOCAL writeemptypage(); XLOCAL scanpages(); XLOCAL writestring(); XLOCAL message(); X Xextern long lseek(); Xextern long ftell(); Xextern char *getenv(); END_OF_psutil.h if test 663 -ne `wc -c <psutil.h`; then echo shar: \"psutil.h\" unpacked with wrong size! fi # end of overwriting check fi echo shar: End of archive 1 \(of 1\). cp /dev/null ark1isdone MISSING="" for I in 1 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 1 archives. rm -f ark[1-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0 -- Angus Duggan, Department of Computer Science, | I'm pink, therefore I'm Spam. University of Edinburgh, JCMB, | JANET: ajcd@uk.ac.ed.lfcs The King's Buildings, Mayfield Road, | VOICE: (UK) 031 650 5126 Edinburgh, EH9 3JZ, Scotland. | OR: ajcd%lfcs.ed.ac.uk@nsfnet-relay.ac.uk
ajcd@cs.edinburgh.ac.uk (Angus Duggan) (04/10/91)
In article <8545@skye.cs.ed.ac.uk>, ajcd@cs.edinburgh.ac.uk (me) writes: > People seem to have been asking for programs to perform page manipulation for > PostScript recently; here's my set of utilities for selecting and rearranging > pages from PostScript documents. > > #! /bin/sh ... This always happens to me; post something, then instantly start finding stupid bugs. I copied some stuff from the red book incorrectly, here's a patch: *** pstops.c.~1~ Wed Feb 20 14:06:57 1991 --- pstops.c Tue Apr 9 16:57:44 1991 *************** *** 296,302 **** } writestring("/initgraphics{initmatrix newpath initclip 1 setlinewidth\n"); writestring(" 0 setlinecap 0 setlinejoin []0 setdash 0 setgray\n"); ! writestring(" 0 setmiterlimit}bind def\n"); if (nobinding) /* desperation measures */ writestring("/bind{}def\n"); writestring("%%EndProcSet\n"); --- 296,302 ---- } writestring("/initgraphics{initmatrix newpath initclip 1 setlinewidth\n"); writestring(" 0 setlinecap 0 setlinejoin []0 setdash 0 setgray\n"); ! writestring(" 10 setmiterlimit}bind def\n"); if (nobinding) /* desperation measures */ writestring("/bind{}def\n"); writestring("%%EndProcSet\n"); a. -- Angus Duggan, Department of Computer Science, | I'm pink, therefore I'm Spam. University of Edinburgh, JCMB, | JANET: ajcd@uk.ac.ed.lfcs The King's Buildings, Mayfield Road, | VOICE: (UK) 031 650 5126 Edinburgh, EH9 3JZ, Scotland. | OR: ajcd%lfcs.ed.ac.uk@nsfnet-relay.ac.uk