From: mike@media-lab.mit.edu (Michael Hawley)
Subject: quarto
Reply-To: mike@media-lab.mit.edu
Organization: MIT Media Laboratory
Date: Mon, 16 Nov 1992 02:31:53 GMT

There have been a few more requests for an "N-up" program
(that works with embedded EPS illustrations)
ways of listing pages, printing booklets in signatures, etc.
Here's a simple one I use on the NeXT (known to work on other
Unix machines).
    quarto -l your-dissertation.ps
lists the pages, and
    quarto -2 your-nobel-prize-acceptance-speech.ps | lpr
prints 2 pages per sheet.
    quarto -8 ... | quarto -3 | ...
prints 8*3 = 24 little pages per sheet, if that's really what you want.

- - - -
/*
 * quarto.c
 *
 * Quickie program to filter PostScript (EPS) files and...
 *   - extract selected pages
 *         ... | quarto -p1,2,6-8 ...    print those pages
 *   - change page order (reverse, even, odd; etc)
 *         ... | quarto -r ...           print in reverse order
 *         ... | quarto -p odd ...       select the odd pages
 *   - print "N-up" (quarto/octavo; N=2,3,4,6,8,9) layouts
 *         ... | quarto -4 -c -b ...     print 4 (small) pages per side
 *
 * For printing books or booklets in signatures, e.g.,
 *   quarto -2 -S 4 ...
 * means that a signature is 4 sheets, 2 (small) pages per side.
 * The page ordering is such that you can run them through a copier
 * printing 2 pages onto 1 double-sided page (or take every other sheet,
 * flip it over, and glue it back to back on the previous sheet).
 *
 * Currently only tested and used with a NeXT printer + Letter-size  
paper,
 * with input in portrait orientation.  It should be easy to fix for
 * other varieties; check PBox, the test pattern, the calculation of  
'Scale',
 * and the macro 'pgx()' (used by the 'putNup()' routines).  If you can
 * print out test patterns (the "-d" option) you're close if not  
working.
 *
 * Enjoy at your own peril, but please send me any improvements.
 *
 * Also, if you like this program and get good use out of it, consider
 * sending $4.37 to:
 *    Bob Greene
 *    Media Lab Crew Treasurer
 *    MIT Media Lab
 *    20 Ames Street
 *    Cambridge, MA 02139
 * 
 *    Say "Yo Bob!  I'm dying to find out how your boys did at Henley.
 *         Did they really row in the nude because you are too poor
 *         to buy uniforms?  Perhaps this miserly contribution will  
help."
 *
 *
 * M. J. Hawley
 * MIT media Laboratory
 * 20 Ames Street
 * Cambridge, MA 02139
 * mike@media-lab.mit.edu
 * Copyright (c) MIT Media Laboratory 1991.
 * Top secret!  Burn before reading!  This means you!!
 *
 *
 * Notes -- version 1.1
 *
 * - added signature printing and fixed the nested-page bug (November,  
1992)
 */
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>

/*
 * Some general utilities ----------------------------------------
 */
char *av0;
#define Case break; case
#define Default break; default
static char *_arg, *_argp; /* use by 'for_each_argument */
extern char *av0;       /* will hold name of the command */
#define argument  (_arg=(*_argp? _argp : av[++i==ac? --i :  
i]),_argp+=strlen(_argp),_arg)
#define for_each_argument av0 = av[0]; for (i=1;i<ac &&  
*av[i]=='-';i++)\
                        for (_argp = &av[i][1]; *_argp;)\
                                switch(*_argp++)

extern char *malloc(), *index();
extern int atoi();
extern double atof();

stripnl(s) char *s; { /* remove trailing \n and space from 's' */
    char *p = s + strlen(s)-1;
    while (p>s && (*p=='\n' || *p == ' ')) *p-- = '\0';
}

char *
skipsp(s) char *s; {
    while (*s==' ' || *s=='\t' || *s == '\n') ++s;
    return s;
}

error(a,b,c,d,e,f) int a,b,c,d,e,f; { /* printf an error msg */
    fprintf(stderr,(char *)a,b,c,d,e,f); fprintf(stderr,"\n");
}

/* ---------------------------------------------------------------- */

FILE *Input, *Output;
char *CurFile;

typedef struct { float x, y; } Point;
typedef struct { Point o, c; } Rectangle;
#define HS(r) (r.c.x-r.o.x)
#define VS(r) (r.c.y-r.o.y)

Rectangle BBox = { 0, 0, 612, 792 }; /* bbox of the document */

Rectangle PBox = { 10, 14, 600, 780 }; /* Letter-sized page-bounding box  
*/
                 /* These values probably depend on the printer and
                  * paper size; use the test pattern to adjust them for  
now.
                  */
Rectangle NullRect = {0,0,0,0};

int TestPattern = 0;    /* if true, overlay a test pattern on the page*/
int ListPages = 0;      /* if true, list pages in the document (no  
print) */

#define _N_up 1
int N_up = 1;           /* default # pages to fit on a sheet */

#define _Gutter 12.
float Gutter= _Gutter;
#define _CutMarks 0
#define _Borders  1
int CutMarks=_CutMarks, /* print cutmarks around shrunken pages */
    Borders=_Borders;   /* print dashed borders around shrunken pages */
int ReversePages = 0;
int Signatures = 0;	/* determine signature page order */

float Scale = .45;	/* Scale is normally set by the tiling routines;  
*/
int ScaleSet = 0;	/* made a user option just in case. */
int Rotate = 0;         /* will be true when landscaping for -2,6,8 */

#define MaxP 1024
#define Trailer (MaxP-1)
int Page[MaxP], PageLen[MaxP];
Rectangle PageBbox[MaxP], pageBbox;
int NP=0;

findPageOffsets(){
    char s[4192], *s2=s+2;
    int i,prevpos=0,prevp=0,p,nested=0;
    FILE *f = Input;

    for(i=0;i<MaxP;i++){
        Page[i]=PageLen[i]=0;
        PageBbox[i] = NullRect;
    }

    rewind(f);
    while (fgets(s,sizeof s,f)){
     if (s[0]=='%' && s[1]=='%'){
      if (strncmp(s2,"BeginDocument",13)==0 ||
          strncmp(s2,"BeginBinary",11)==0){
            nested++;
      } else
      if (strncmp(s2,"EndDocument",11)==0 ||
          strncmp(s2,"EndBinary",9)==0){
            if (nested>0) nested--;
      } else
      if (nested==0){
        if (strncmp(s2,"Page:",5)==0){
            sscanf(s,"%%%%Page: %*s %d",&p);
            Page[p] = ftell(f)-strlen(s);
            PageLen[prevp] = Page[p] - prevpos;
            prevp = p;
            prevpos = Page[p];
            NP++;
        } else
        if (strncmp(s2,"Trailer",7)==0){
            Page[Trailer] = ftell(f)-strlen(s);
            PageLen[prevp] = Page[Trailer] - prevpos;
            prevpos = Page[Trailer];
        } else
        if (strncmp(s2,"BoundingBox:",12)==0){
            char *q = skipsp(index(s,':')+1);
            if (isdigit(*q))
                sscanf(q,"%f %f %f %f",
                         &BBox.o.x,&BBox.o.y,&BBox.c.x,&BBox.c.y);
        } else
        if (strncmp(s2,"PageBoundingBox:",16)==0){
            Rectangle r;
            char *q = skipsp(index(s,':')+1);
            if (isdigit(*q)){
                sscanf(q,"%f %f %f %f", &r.o.x,&r.o.y,&r.c.x,&r.c.y);
                if (NP) PageBbox[NP] = r;
            }
        }
      }
     }
    }
    PageLen[Trailer] = ftell(f) - prevpos;
    pageBbox = PageBbox[1]; /* hack ... */
        /*
         * Some NeXT programs put out PageBoundingBoxes
         * with various origins for each page,
         * but draw relative to the *first* PageBoundingBox.
         * See "hack", below.
         */
}

showPageOffsets() {
    char s[4192];
    int i,j;
    FILE *f = Input;

    printf("file: %s, %d pages\n",CurFile,NP);
    printf("%8s %8s\n","offset","length");
    for (i=j=0;j<=NP;i++){
        if (Page[i] || i==0){
            fseek(f,Page[i],0);
            fgets(s,sizeof s,f);
            printf("%8d %8d -- %s%s",Page[i], PageLen[i],i?"":"(header)  
",s);
            j++;
        }
    }
    fseek(f,Page[Trailer],0);
    fgets(s,sizeof s,f);
    printf("%8d %8d -- (trailer) %s",Page[Trailer],PageLen[Trailer],s);
}

static char *_s = (char *)0;
static int _sn = 0;

char *
readPage(n){
    size_t r;
    if (_s) *_s = '\0';
    if (Page[n] || (n==0 && PageLen[n])){
        if (_sn < PageLen[n]){
            _sn = 2*PageLen[n];
            _s = (_s? (char *)realloc(_s,_sn) : (char *)malloc(_sn));
            if (!_s) error("%s: no memory!",av0), exit(1);
        }
        rewind(Input);
        fseek(Input,Page[n],0);
        *_s = '\0';
        fread(_s,PageLen[n], (size_t)1, Input);
    }
    return _s;
}

copyPage(n)
/*
 * Write the 'n'th page in 'f' onto 'o'.
 */
{
    char *s = readPage(n);
    if (*s) fwrite(s,PageLen[n],1,Output);
}

P(a,b,c,d,e,f,g,h,i){
    fprintf(Output,(char *)a,b,c,d,e,f,g,h,i);
    fprintf(Output,"\n");
}

putProlog(n){
    char *date, *ctime();
    time_t t;

    time(&t); date = ctime(&t); stripnl(date);
    P("%%!PS-Adobe-2.0");
    P("%%%%Creator: %s, file: %s",av0,CurFile);
    P("%%%%CreationDate: %s",date);
    P("%%%%Pages: (atend)",n);
    P("%%%%BoundingBox: 0 0 612 792");  /* should have options to set */
    P("%%%%EndComments");
    P("");
    P("%% ---------------------- (routines for \"%s\")",av0);
    P("/q_W %f def  /q_H %f def  /q_margin 4 def",BBox.c.x,BBox.c.y);
    P("/q_factor %f def",Scale);
    P("/q_bbox {");
    if (Borders){
	P("    /q_y 0 q_factor mul def");
	P("    /q_x 0 q_factor mul def");
	P("    /q_w q_W q_margin 2 mul add def");
	P("    /q_h q_H q_margin 2 mul add def");
	P("    q_x q_margin sub q_y q_margin sub moveto");
	P("    .2 setgray");
	P("    0 setlinewidth");
	P("    q_w 0 rlineto");
	P("    0 q_h rlineto");
	P("    0 q_w sub 0 rlineto");
	P("    0 0 q_h sub rlineto");
	P("    stroke");
    }
    P("} def");
    P("");
    P("/q_cuts {");
    if (CutMarks){
	P("    /q_y 0 q_factor mul q_margin sub def");
	P("    /q_x 0 q_factor mul q_margin sub def");
	P("    /q_w q_W q_margin 2 mul add def");
	P("    /q_h q_H q_margin 2 mul add def");
	P("    /q_d 10 q_factor mul def");
	P("    0 setgray");
	P("    0 setlinewidth");
	P("    q_x q_d sub q_y moveto -1 -1 rmoveto");
	P("        q_d 0 rlineto 0 0 q_d sub rlineto stroke");
	P("    q_x q_d add q_w add q_y moveto 1 -1 rmoveto");
	P("        0 q_d sub 0 rlineto 0 0 q_d sub rlineto stroke");
	P("    q_x q_d add q_w add q_y q_h add moveto 1 1 rmoveto");
	P("        0 q_d sub 0 rlineto 0 q_d rlineto stroke");
	P("    q_x q_d sub q_y q_h add moveto -1 1 rmoveto");
	P("        q_d 0 rlineto 0 0 q_d rlineto stroke");
    }
    P("} def");
    P("");
    P("/StartShrunkenPage {");
    P("    /q_y exch def /q_x exch def");
    P("    gsave");
    P("    q_x q_y translate");
    P("    q_factor q_factor scale");
    if (Rotate) P("    -90 rotate");
    P("} def");
    P("");
    P("/EndShrunkenPage {");
    P("    grestore");
    P("} def");
    P("");
    if (N_up!=1){
       P("/oldshowpage {showpage} bind def");
       P("/showpage { } bind def");
    }
    P("");
    P("/q_circ { %% x y size ... draw circle + crosshairs");
    P("    /q_size exch def /q_y exch def /q_x exch def");
    P("    0 setlinewidth");
    P("    q_x q_y moveto");
    P("    q_size 0 rlineto");
    P("    -2 q_size mul 0 rlineto");
    P("    q_size 0 rlineto");
    P("    0 q_size rlineto");
    P("    0 -2 q_size mul rlineto");
    P("    stroke");
    P("    q_x q_y moveto");
    P("    q_x q_y q_size 2 div 0 360 arc stroke");
    P("} def");
    P("");
    P("/q_box { %% lx ly ux uy ... draw a box");
    P("    /q_uy exch def /q_ux exch def /q_ly exch def /q_lx exch  
def");
    P("    q_lx q_ly moveto");
    P("    q_ux q_ly lineto");
    P("    q_ux q_uy lineto");
    P("    q_lx q_uy lineto");
    P("    q_lx q_ly lineto");
    P("    stroke");
    P("} def");
    P("");
    P("/q_testPattern {");
    P("    0 setgray 0 setlinewidth");
    P("    %f %f %f %f q_box", PBox.o.x,PBox.o.y,PBox.c.x,PBox.c.y);
    P("    %f %f moveto 8 0 rlineto stroke",  
PBox.o.x-4,VS(PBox)/2+PBox.o.y);
    P("    %f %f moveto -8 0 rlineto stroke",  
PBox.c.x+4,VS(PBox)/2+PBox.o.y);
    P("    %f %f moveto 0 8 rlineto stroke",  
HS(PBox)/2+PBox.o.x,PBox.o.y-4);
    P("    %f %f moveto 0 -8 rlineto stroke",  
HS(PBox)/2+PBox.o.x,PBox.c.y+4);
    P("    %f %f 5 q_circ",HS(PBox)/2+PBox.o.x,VS(PBox)/2+PBox.o.y);
    P("} def");
    P("");
    P("%% ----------------------(end of \"%s\" routines)\n",av0);
    copyPage(0);  /* put out the previous document header page */
}

ShowPage(){
    if (TestPattern) P("q_testPattern");
    P("q_sheet restore\noldshowpage");
}

putTrailer(n){
    copyPage(Trailer);
    P("%%%%Pages: %d %d",n,n);
}

static Point
center(r) Rectangle r; {
    Point c;
    c.x = HS(r)/2. + r.o.x;
    c.y = VS(r)/2. + r.o.y;
    return c;
}

static Point
add(a,b) Point a,b; {
    a.x += b.x;
    a.y += b.y;
    return a;
}

static Point
sub(a,b) Point a,b; {
    a.x -= b.x;
    a.y -= b.y;
    return a;
}

hideOriginalPages(s) char *s; {
    s[2] = 'p';
    while ((s = index(s,'\n')+1) != (char *)1)
        if (strncmp(s,"%%Page:",7)==0) s[2] = 'p'; 
}

pput(n,p) Point p; {
    char *s = readPage(n);
    Rectangle r;
    if (*s){
        hideOriginalPages(s);
        r = pageBbox; /* PageBbox[n]; ... Thus endeth the hack. */
	P("%f %f StartShrunkenPage",p.x,p.y);
	P("%f %f translate",-1.*Scale*r.o.x,-1.*Scale*r.o.y);
        fwrite(s,PageLen[n],1,Output);
        P("EndShrunkenPage");
    }
}

float
fmin(a,b) float a,b; {
    return a<b? a : b;
}

#define maxQ 16

#define up_decls \
    int i, j, N = (NP+(N_up-1))/N_up, page[maxQ];\
    float gap = Gutter;\
    Point c, p, g, b, q[maxQ];\
    g.x=g.y = gap/2.; b = BBox.c

#define nup_init() c = center(PBox); \
                   b.x *= Scale; b.y *= Scale; \
                   putProlog(N)

#define pgx(inc,d) pput(i*N_up+j+1,p); page[j] = *_s; q[j++]=p; p.d +=  
inc
#define putbox() for (j=0; j<N_up;j++) \
                   if (page[j])\
                    P("%f %f StartShrunkenPage q_bbox q_cuts  
EndShrunkenPage",\
                        q[j].x,q[j].y); \
                 ShowPage()


put2up(){
    up_decls;

    if (!ScaleSet)
        Scale =  
fmin((VS(PBox)/2.-(2.*gap))/b.x,((HS(PBox)-(2.*gap))/b.y));
    nup_init();

    for (i=0;i<N;i++){
	P("\n%%%%Page: %d %d\n/q_sheet save def",i+1,i+1);
        j = 0;

        p = c; p.y += g.x + b.x; p.x -= b.y/2.;
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
	
        putbox();   /* put out boxes, cut marks, & ShowPage */
    }
    return N;
}

put3up(){
    up_decls;

    if (!ScaleSet)
        Scale =  
fmin((VS(PBox)/3.-(2.*gap))/b.x,((HS(PBox)-(2.*gap))/b.y));
    nup_init();

    for (i=0;i<N;i++){
	P("\n%%%%Page: %d %d\n/q_sheet save def",i+1,i+1);
        j = 0;

        p = c; p.y += g.x + 1.5*b.x; p.x -= b.y/2.;
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
	
        putbox();   /* put out boxes, cut marks, & ShowPage */
    }
    return N;
}

put6up(){
    up_decls;

    if (!ScaleSet)
        Scale =  
fmin((VS(PBox)/3.-(2.*gap))/b.x,((HS(PBox)/2.-(2.*gap))/b.y));
    nup_init();

    for (i=0;i<N;i++){
	P("\n%%%%Page: %d %d\n/q_sheet save def",i+1,i+1);
        j = 0;

        p = c; p.y += gap + 1.5*b.x; p.x += g.x;
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
	
        p = c; p.y += gap + 1.5*b.x; p.x -= g.x + b.y;
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
	
        putbox();   /* put out boxes, cut marks, & ShowPage */
    }
    return N;
}

put8up(){
    up_decls;

    if (!ScaleSet)
        Scale =  
fmin((VS(PBox)/4.-(2.*gap))/b.x,((HS(PBox)/2.-(2.*gap))/b.y));
    nup_init();

    for (i=0;i<N;i++){
	P("\n%%%%Page: %d %d\n/q_sheet save def",i+1,i+1);
        j = 0;

        p = c; p.y += 1.5*gap + 2.*b.x; p.x += g.x;
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
	
        p = c; p.y += 1.5*gap + 2.*b.x; p.x -= g.x + b.y;
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
        pgx(-(gap+b.x),y);
	
        putbox();   /* put out boxes, cut marks, & ShowPage */
    }
    return N;
}

put4up(){
    up_decls;

    if (!ScaleSet)
        Scale =  
5*fmin((HS(PBox)-(2.*gap))/b.x,(VS(PBox)-(2.*gap))/b.y);
    nup_init();

    for (i=0;i<N;i++){
	P("\n%%%%Page: %d %d\n/q_sheet save def",i+1,i+1);
        j = 0;

        p = c; p.x -= g.x + b.x; p.y += g.y;
        pgx(gap+b.x,x);
        pgx(gap+b.x,x);
        p = c; p.x -= g.x + b.x; p.y -= g.y + b.y;
        pgx(gap+b.x,x);
        pgx(gap+b.x,x);
	
        putbox();   /* put out boxes, cut marks, & ShowPage */
    }
    return N;
}

put9up(){
    up_decls;

    if (!ScaleSet)
        Scale =  
333*fmin((HS(PBox)-(3.*gap))/b.x,(VS(PBox)-(2.*gap))/b.y);
    nup_init();

    for (i=0;i<N;i++){
	P("\n%%%%Page: %d %d\n/q_sheet save def",i+1,i+1);
        j = 0;

        p = c; p.x -= g.x + 1.5*b.x; p.y += g.y + .5*b.y;
        pgx(g.x+b.x,x);
        pgx(g.x+b.x,x);
        pgx(g.x+b.x,x);

        p = c; p.x -= g.x + 1.5*b.x; p.y -= .5*b.y;
        pgx(g.x+b.x,x);
        pgx(g.x+b.x,x);
        pgx(g.x+b.x,x);
	
        p = c; p.x -= g.x + 1.5*b.x; p.y -= g.y + 1.5*b.y;
        pgx(g.x+b.x,x);
        pgx(g.x+b.x,x);
        pgx(g.x+b.x,x);

        putbox();   /* put out boxes, cut marks, & ShowPage */
    }
    return N;
}

putpages(){
    int i;
    char *s, *p, *index();

    putProlog(NP);
    for (i=1;i<=NP;i++){
        s = readPage(i);
        if (*s){
            p = index(s,'\n');
            if (p){
                p+1;
                fprintf(Output,"%%%%Page: %d %d\n",i,i);
                fwrite(p,PageLen[i] - (p-s),1,Output);
                fprintf(Output,"q_bbox\n");
            }
        } else { /* emit blank page, e.g., for signatures */
                fprintf(Output,"%%%%Page: %d %d\n",i,i);
                fprintf(Output,"q_bbox\n");
        }
    }
}

char *SelectPages = (char *)0;

ReorderPages(){
    int i,j;
    int pages[MaxP], po[MaxP], pl[MaxP], np=0;
    Rectangle pr[MaxP];

    bzero(pages,sizeof(pages));

    if (SelectPages){
        char *s = skipsp(SelectPages);
        int start=0,end;
        while (*s){
            if (strncmp(s,"even",4)==0){
                for (i=2;i<=NP;i+=2) pages[np++]=i;
                s+=4;
            } else
            if (strncmp(s,"odd",3)==0){
                for (i=1;i<=NP;i+=2) pages[np++]=i;
                s+=3;
            } else
            if (strncmp(s,"blank",5)==0){
                pages[np++]=NP+1;
                s+=5;
            } else
            if (strncmp(s,"all",3)==0){
                for (i=1;i<=NP;i++) pages[np++]=i;
                s+=3;
            } else
            if (*s == '-'){
                start = 1;
                ++s;
                end = atoi(s);
                while (isdigit(*s)) ++s;
                for (i=start;i<=end;i++) pages[np++] = i;
            } else
            if (isdigit(*s)){
                pages[np++] = atoi(s);
                while (isdigit(*s) || isspace(*s)) ++s;
                if (*s == '-'){
                    ++s;
                    while (*s && !isdigit(*s)) ++s;
                    end = *s? atoi(s) : NP;
                    while (*s && isdigit(*s)) ++s;
                    for (i=pages[np-1]+1;i<=end;i++) pages[np++] = i;
                }
            } else
                ++s;
            while (*s== ',' || *s=='-' || isspace(*s)) ++s;
        }
        for (i=0;i<np;i++){
            po[i] = Page[pages[i]];
            pl[i] = PageLen[pages[i]];
            pr[i] = PageBbox[pages[i]];
        }
        for (i=0;i<np;i++){
            Page[i+1] = po[i];
            PageLen[i+1] = pl[i];
            PageBbox[i+1] = pr[i];
        }
        for (i=np+1;i<=NP;i++) Page[i] = PageLen[i] = 0;
        NP = np;
    } else
        for (i=0;i<NP;i++) pages[i] = i+1;
    if (ReversePages){
#define swap(t, a,b) t = a; a = b; b = t
        for (i=1,j=NP; i<j; i++,j--){
            int t;
            Rectangle r;
            swap(t, pages[i],pages[j]);
            swap(t, Page[i],Page[j]);
            swap(t, PageLen[i],PageLen[j]);
            swap(r,PageBbox[i],PageBbox[j]);
        }
    }
    if (Signatures){
      /* reorder appropriately for 2-sided signatures of
         'Signature' sheets per packet.
         With signatures of 2 sheets, a page ordering of
         1 2 3 4 5 6 7 8 9 10 11 12 . . .
         becomes
         8 1 2 7 6 3 4 5    16 9 10 15 . . .
       */
	int i, p, s, *P, tmp, PP, page[MaxP];
	int SidesPerSig = Signatures*2,
	    PagesPerSig = SidesPerSig*2,
	    NumSigs = (NP + PagesPerSig - 1) / PagesPerSig;
        bzero(page,sizeof(page));
	for (s=0,i=0; s<NumSigs; s++){
	    for (p=0, P = page+s*PagesPerSig+1;p<SidesPerSig;p++, P+=2)
		*P = pages[i++];
	    for (p=0, P-=3 ;p<SidesPerSig;p++, P-=2)
		*P = pages[i++];
	    for (p=0, P=page+s*PagesPerSig+2; p<PagesPerSig;p++,P+=4)
		tmp=P[0],P[0]=P[1],P[1]=tmp;
	}
        PP = NP; NP = PagesPerSig*NumSigs;
        for (i=0;i<NP;i++) if (page[i]>PP || !page[i]) page[i]=NP;

        for (i=0;i<NP;i++){
            po[i+1] = Page[page[i]];
            pl[i+1] = PageLen[page[i]];
            pr[i+1] = PageBbox[page[i]];
        }
        for (i=0;i<NP;i++){
            Page[i+1] = po[i+1];
            PageLen[i+1] = pl[i+1];
            PageBbox[i+1] = pr[i+1];
        }
	fprintf(stderr,"%d %d-page signatures:\n  ",NumSigs,Signatures);
	for (i=0;i<NP;i++){
	    if (i && i%4 == 0) fprintf(stderr,"  ");
	    if (i && i%PagesPerSig == 0) fprintf(stderr,"\n  ");
	    if (page[i]==NP)
		fprintf(stderr," . ");
	    else
		fprintf(stderr,"%2d ",page[i]);
	}
	fprintf(stderr,"\n");
    }
}

doFile(s) char *s; {
    FILE *f = fopen(s,"r");
    int n;
    if (!f) return error("%s: couldn't open %s",av0,s);
    Input = f;
    findPageOffsets();

    if (ListPages)
        showPageOffsets();
    else {
	ReorderPages();
    
	switch (N_up){
	case 2: n=put2up();
	Case 3: n=put3up();
	Case 4: n=put4up();
	Case 6: n=put6up();
	Case 8: n=put8up();
	Case 9: n=put9up();
	Default:n=putpages();
	}
    
	putTrailer(n);
    }
    fclose(f);
}

savetmp(s,f) char *s; FILE *f;
/* 
 * Copy 'f' into a tmp file; put the name in s,
 * and return 1 if success.
 */
{
    FILE *o;
    int n, F, O;
    char b[8192];
    strcpy(s,"/tmp/quartoXXXXXX");
    mktemp(s);
    strcat(s,".eps");
    if (o = fopen(s,"w")){
        F = fileno(f);
        O = fileno(o);
        while ((n=read(F,b,sizeof b))>0)
            write(O,b,n);
        fclose(o);
        return 1;
    } else
        return 0;    
}

use(){
    error("use: %s [-[234689]] [-p <pages>] [-S #] [-bcdlr] [-g #] [PS  
file]",av0);
    error("Select pages from a PostScript file, and print in a tiled  
format.");
    error("(The input must contain EPS-like directives  
'%%%%Page:...')");
    error("  -p... print the given pages; e.g., '-p1,2,4-8,12-'");
    error("        '-p -4' prints through page four;");
    error("        '-p 12-' prints from page 12 through the end;");
    error("        numbers are original ordinal page numbers, starting  
at 1");
    error("        also, '-p even', '-p odd', '-p1-4,1-4,1-4,1-4'  
etc.");
    error("        'blank' or 0 inserts a blank page; 'all' inserts all  
the pages");
    error("  -2    print 2 tiled, shrunken pages per page;");
    error("        # may be 2,3,4,6,8 or 9 (default 1);");
    error("        portrait or landscape layout will be chosen  
accordingly.");
    error("  -b    %sprint borders around each tiled page.",
                   _Borders?"don't ":"");
    error("  -c    %sprint cut-marks for each tiled page.",
                   _CutMarks?"don't ":"");
    error("  -d    overlay a test pattern on the page.");
    error("        (a box around the paper sheet border, ");
    error("         and a circle and cross in the middle;");
    error("         intended to help calibrate for different  
printers)");
    error("  -S #  reorder pages to print them in signatures of #  
sheets;");
    error("        e.g., '-2 -S 3' prints pages 2-up per page for  
signatures");
    error("        of 3 double-sided sheets that will be folded and  
bound together.");
    error("  -g #  set the gap between tiles to # (%3f)",_Gutter);
    error("  -l    list the pages+offsets (don't output any  
PostScript).");
    error("  -r    reverse the page order");
    exit(1);
}

main(ac,av) char *av[]; {
    int i;

    for_each_argument {
    case 'l': ListPages = 1;
    Case 'p': SelectPages = argument;
    Case 'r': ReversePages = 1;
    Case 'd': TestPattern = !TestPattern;
    Case 'c': CutMarks = !CutMarks;
    Case 'b': Borders = !Borders;
    Case '1': N_up = 1;
    Case '2': N_up = 2; Rotate = 1;
    Case '3': N_up = 3; Rotate = 1;
    Case '4': N_up = 4;
    Case '6': N_up = 6; Rotate = 1;
    Case '8': N_up = 8; Rotate = 1;
    Case '9': N_up = 9;
    Case 'g': Gutter = atof(argument);
    Case 's': Scale = atof(argument); ScaleSet++; /* not mentioned as  
option */
    Case 'S': Signatures = atof(argument);
              if (N_up != 2)
                 error("You may want to specify -2 sheets per page.");
    Default : use();
    }

    Output = stdout;
    if (i==ac){
        char s[1024];
        if (savetmp(s,stdin))
            CurFile="stdin", doFile(s), unlink(s);
        else
            error("%s: couldn't save stdin to a tmp file.",av0),  
exit(1);
    } else
    if ((i+1)==ac)
        CurFile=av[i], doFile(av[i++]);
    else
        error("%s: accepts only one file, or stdin...\n",av0), use();
    exit(0);
}
Newsgroups: comp.lang.postscript