450 lines
9 KiB
C
450 lines
9 KiB
C
/*
|
||
* 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;
|
||
}
|