Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * ps.c
 * 
 * provide postscript file reading support for page
 */

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <bio.h>
#include <ctype.h>
#include "page.h"

typedef struct PSInfo   PSInfo;
typedef struct Page     Page;
        
struct Page {
        char *name;
        int offset;                     /* offset of page beginning within file */
};

struct PSInfo {
        GSInfo;
        Rectangle bbox; /* default bounding box */
        Page *page;
        int npage;
        int clueless;   /* don't know where page boundaries are */
        long psoff;     /* location of %! in file */
        char ctm[256];
};

static int      pswritepage(Document *d, int fd, int page);
static Image*   psdrawpage(Document *d, int page);
static char*    pspagename(Document*, int);

#define R(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
Rectangle
rdbbox(char *p)
{
        Rectangle r;
        int a;
        char *f[4];
        while(*p == ':' || *p == ' ' || *p == '\t')
                p++;
        if(tokenize(p, f, 4) != 4)
                return Rect(0,0,0,0);
        r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
        r = canonrect(r);
        if(Dx(r) <= 0 || Dy(r) <= 0)
                return Rect(0,0,0,0);

        if(truetoboundingbox)
                return r;

        /* initdraw not called yet, can't use %R */
        if(chatty) fprint(2, "[%d %d %d %d] -> ", R(r));
        /*
         * attempt to sniff out A4, 8½×11, others
         * A4 is 596×842
         * 8½×11 is 612×792
         */

        a = Dx(r)*Dy(r);
        if(a < 300*300){        /* really small, probably supposed to be */
                /* empty */
        } else if(Dx(r) <= 596 && r.max.x <= 596 && Dy(r) > 792 && Dy(r) <= 842 && r.max.y <= 842)      /* A4 */
                r = Rect(0, 0, 596, 842);
        else {  /* cast up to 8½×11 */
                if(Dx(r) <= 612 && r.max.x <= 612){
                        r.min.x = 0;
                        r.max.x = 612;
                }
                if(Dy(r) <= 792 && r.max.y <= 792){
                        r.min.y = 0;
                        r.max.y = 792;
                }
        }
        if(chatty) fprint(2, "[%d %d %d %d]\n", R(r));
        return r;
}

#define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y

int
prefix(char *x, char *y)
{
        return strncmp(x, y, strlen(y)) == 0;
}

/*
 * document ps is really being printed as n-up pages.
 * we need to treat every n pages as 1.
 */
void
repaginate(PSInfo *ps, int n)
{
        int i, np, onp;
        Page *page;

        page = ps->page;
        onp = ps->npage;
        np = (ps->npage+n-1)/n;

        if(chatty) {
                for(i=0; i<=onp+1; i++)
                        print("page %d: %d\n", i, page[i].offset);
        }

        for(i=0; i<np; i++)
                page[i] = page[n*i];

        /* trailer */
        page[np] = page[onp];

        /* EOF */
        page[np+1] = page[onp+1];

        ps->npage = np;

        if(chatty) {
                for(i=0; i<=np+1; i++)
                        print("page %d: %d\n", i, page[i].offset);
        }

}

Document*
initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
{
        Document *d;
        PSInfo *ps;
        char *p;
        char *q, *r;
        char eol;
        char *nargv[1];
        char fdbuf[20];
        char tmp[32];
        int fd;
        int i;
        int incomments;
        int cantranslate;
        int trailer=0;
        int nesting=0;
        int dumb=0;
        int landscape=0;
        long psoff;
        long npage, mpage;
        Page *page;
        Rectangle bbox = Rect(0,0,0,0);

        if(argc > 1) {
                fprint(2, "can only view one ps file at a time\n");
                return nil;
        }

        fprint(2, "reading through postscript...\n");
        if(b == nil){   /* standard input; spool to disk (ouch) */
                fd = spooltodisk(buf, nbuf, nil);
                sprint(fdbuf, "/fd/%d", fd);
                b = Bopen(fdbuf, OREAD);
                if(b == nil){
                        fprint(2, "cannot open disk spool file\n");
                        wexits("Bopen temp");
                }
                nargv[0] = fdbuf;
                argv = nargv;
        }

        /* find %!, perhaps after PCL nonsense */
        Bseek(b, 0, 0);
        psoff = 0;
        eol = 0;
        for(i=0; i<16; i++){
                psoff = Boffset(b);
                if(!(p = Brdline(b, eol='\n')) && !(p = Brdline(b, eol='\r'))) {
                        fprint(2, "cannot find end of first line\n");
                        wexits("initps");
                }
                if(p[0]=='\x1B')
                        p++, psoff++;
                if(p[0] == '%' && p[1] == '!')
                        break;
        }
        if(i == 16){
                werrstr("not ps");
                return nil;
        }

        /* page counting */
        npage = 0;
        mpage = 16;
        page = emalloc(mpage*sizeof(*page));
        memset(page, 0, mpage*sizeof(*page));

        cantranslate = goodps;
        incomments = 1;
Keepreading:
        while(p = Brdline(b, eol)) {
                if(p[0] == '%')
                        if(chatty > 1) fprint(2, "ps %.*s\n", utfnlen(p, Blinelen(b)-1), p);
                if(npage == mpage) {
                        mpage *= 2;
                        page = erealloc(page, mpage*sizeof(*page));
                        memset(&page[npage], 0, npage*sizeof(*page));
                }

                if(p[0] != '%' || p[1] != '%')
                        continue;

                if(prefix(p, "%%BeginDocument")) {
                        nesting++;
                        continue;
                }
                if(nesting > 0 && prefix(p, "%%EndDocument")) {
                        nesting--;
                        continue;
                }
                if(nesting)
                        continue;

                if(prefix(p, "%%EndComment")) {
                        incomments = 0;
                        continue;
                }
                if(reverse == -1 && prefix(p, "%%PageOrder")) {
                        /* glean whether we should reverse the viewing order */
                        p[Blinelen(b)-1] = 0;
                        if(strstr(p, "Ascend"))
                                reverse = 0;
                        else if(strstr(p, "Descend"))
                                reverse = 1;
                        else if(strstr(p, "Special"))
                                dumb = 1;
                        p[Blinelen(b)-1] = '\n';
                        continue;
                } else if(prefix(p, "%%Trailer")) {
                        incomments = 1;
                        page[npage].offset = Boffset(b)-Blinelen(b);
                        trailer = 1;
                        continue;
                } else if(incomments && prefix(p, "%%Orientation")) {
                        if(strstr(p, "Landscape"))
                                landscape = 1;
                } else if(incomments && Dx(bbox)==0 && prefix(p, q="%%BoundingBox")) {
                        bbox = rdbbox(p+strlen(q)+1);
                        if(chatty)
                                /* can't use %R because haven't initdraw() */
                                fprint(2, "document bbox [%d %d %d %d]\n",
                                        RECT(bbox));
                        continue;
                }

                /*
                 * If they use the initgraphics command, we can't play our translation tricks.
                 */
                p[Blinelen(b)-1] = 0;
                if((q=strstr(p, "initgraphics")) && ((r=strchr(p, '%'))==nil || r > q))
                        cantranslate = 0;
                p[Blinelen(b)-1] = eol;

                if(!prefix(p, "%%Page:"))
                        continue;

                /* 
                 * figure out of the %%Page: line contains a page number
                 * or some other page description to use in the menu bar.
                 * 
                 * lines look like %%Page: x y or %%Page: x
                 * we prefer just x, and will generate our
                 * own if necessary.
                 */
                p[Blinelen(b)-1] = 0;
                if(chatty) fprint(2, "page %s\n", p);
                r = p+7;
                while(*r == ' ' || *r == '\t')
                        r++;
                q = r;
                while(*q && *q != ' ' && *q != '\t')
                        q++;
                free(page[npage].name);
                if(*r) {
                        if(*r == '"' && *q == '"')
                                r++, q--;
                        if(*q)
                                *q = 0;
                        page[npage].name = estrdup(r);
                        *q = 'x';
                } else {
                        snprint(tmp, sizeof tmp, "p %ld", npage+1);
                        page[npage].name = estrdup(tmp);
                }

                /*
                 * store the offset info for later viewing
                 */
                trailer = 0;
                p[Blinelen(b)-1] = eol;
                page[npage++].offset = Boffset(b)-Blinelen(b);
        }
        if(Blinelen(b) > 0){
                fprint(2, "page: linelen %d\n", Blinelen(b));
                Bseek(b, Blinelen(b), 1);
                goto Keepreading;
        }

        if(Dx(bbox) == 0 || Dy(bbox) == 0)
                bbox = Rect(0,0,612,792);       /* 8½×11 */
        /*
         * if we didn't find any pages, assume the document
         * is one big page
         */
        if(npage == 0) {
                dumb = 1;
                if(chatty) fprint(2, "don't know where pages are\n");
                reverse = 0;
                goodps = 0;
                trailer = 0;
                page[npage].name = "p 1";
                page[npage++].offset = 0;
        }

        if(npage+2 > mpage) {
                mpage += 2;
                page = erealloc(page, mpage*sizeof(*page));
                memset(&page[mpage-2], 0, 2*sizeof(*page));
        }

        if(!trailer)
                page[npage].offset = Boffset(b);

        Bseek(b, 0, 2); /* EOF */
        page[npage+1].offset = Boffset(b);

        d = emalloc(sizeof(*d));
        ps = emalloc(sizeof(*ps));
        ps->page = page;
        ps->npage = npage;
        ps->bbox = bbox;
        ps->psoff = psoff;

        d->extra = ps;
        d->npage = ps->npage;
        d->b = b;
        d->drawpage = psdrawpage;
        d->pagename = pspagename;

        d->fwdonly = ps->clueless = dumb;
        d->docname = argv[0];

        if(spawngs(ps, "-dSAFER") < 0)
                return nil;

        if(!cantranslate)
                bbox.min = ZP;
        setdim(ps, bbox, ppi, landscape);

        if(goodps){
                /*
                 * We want to only send the page (i.e. not header and trailer) information
                 * for each page, so initialize the device by sending the header now.
                 */
                pswritepage(d, ps->gsfd, -1);
                waitgs(ps);
        }

        if(dumb) {
                fprint(ps->gsfd, "(%s) run\n", argv[0]);
                fprint(ps->gsfd, "(/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n");
        }

        ps->bbox = bbox;

        return d;
}

static int
pswritepage(Document *d, int fd, int page)
{
        Biobuf *b = d->b;
        PSInfo *ps = d->extra;
        int t, n, i;
        long begin, end;
        char buf[8192];

        if(page == -1)
                begin = ps->psoff;
        else
                begin = ps->page[page].offset;

        end = ps->page[page+1].offset;

        if(chatty) {
                fprint(2, "writepage(%d)... from #%ld to #%ld...\n",
                        page, begin, end);
        }
        Bseek(b, begin, 0);

        t = end-begin;
        n = sizeof(buf);
        if(n > t) n = t;
        while(t > 0 && (i=Bread(b, buf, n)) > 0) {
                if(write(fd, buf, i) != i)
                        return -1;
                t -= i;
                if(n > t)
                        n = t;
        }
        return end-begin;
}

static Image*
psdrawpage(Document *d, int page)
{
        PSInfo *ps = d->extra;
        Image *im;

        if(ps->clueless)
                return readimage(display, ps->gsdfd, 0);

        waitgs(ps);

        if(goodps)
                pswritepage(d, ps->gsfd, page);
        else {
                pswritepage(d, ps->gsfd, -1);
                pswritepage(d, ps->gsfd, page);
                pswritepage(d, ps->gsfd, d->npage);
        }
        /*
         * If last line terminator is \r, gs will read ahead to check for \n
         * so send one to avoid deadlock.
         */
        write(ps->gsfd, "\n", 1);
        im = readimage(display, ps->gsdfd, 0);
        if(im == nil) {
                fprint(2, "fatal: readimage error %r\n");
                wexits("readimage");
        }
        waitgs(ps);

        return im;
}

static char*
pspagename(Document *d, int page)
{
        PSInfo *ps = (PSInfo *) d->extra;
        return ps->page[page].name;
}