diff options
Diffstat (limited to 'src/cmd/page/ps.c')
| -rw-r--r-- | src/cmd/page/ps.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/src/cmd/page/ps.c b/src/cmd/page/ps.c new file mode 100644 index 00000000..46ad5cdb --- /dev/null +++ b/src/cmd/page/ps.c @@ -0,0 +1,450 @@ +/* + * 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 gs; + 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) 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->gs) < 0) + return nil; + + if(!cantranslate) + bbox.min = ZP; + setdim(&ps->gs, 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->gs.gsfd, -1); + waitgs(&ps->gs); + } + + if(dumb) { + fprint(ps->gs.gsfd, "(%s) run\n", argv[0]); + fprint(ps->gs.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->gs.gsdfd, 0); + + waitgs(&ps->gs); + + if(goodps) + pswritepage(d, ps->gs.gsfd, page); + else { + pswritepage(d, ps->gs.gsfd, -1); + pswritepage(d, ps->gs.gsfd, page); + pswritepage(d, ps->gs.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->gs.gsfd, "\n", 1); + im = readimage(display, ps->gs.gsdfd, 0); + if(im == nil) { + fprint(2, "fatal: readimage error %r\n"); + wexits("readimage"); + } + waitgs(&ps->gs); + + return im; +} + +static char* +pspagename(Document *d, int page) +{ + PSInfo *ps = (PSInfo *) d->extra; + return ps->page[page].name; +} |
