plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

ps.c (9072B)


      1 /*
      2  * ps.c
      3  *
      4  * provide postscript file reading support for page
      5  */
      6 
      7 #include <u.h>
      8 #include <libc.h>
      9 #include <draw.h>
     10 #include <cursor.h>
     11 #include <thread.h>
     12 #include <bio.h>
     13 #include <ctype.h>
     14 #include "page.h"
     15 
     16 static int	pswritepage(Document *d, int fd, int page);
     17 static Image*	psdrawpage(Document *d, int page);
     18 static char*	pspagename(Document*, int);
     19 
     20 #define R(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
     21 Rectangle
     22 rdbbox(char *p)
     23 {
     24 	Rectangle r;
     25 	int a;
     26 	char *f[4];
     27 	while(*p == ':' || *p == ' ' || *p == '\t')
     28 		p++;
     29 	if(tokenize(p, f, 4) != 4)
     30 		return Rect(0,0,0,0);
     31 	r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
     32 	r = canonrect(r);
     33 	if(Dx(r) <= 0 || Dy(r) <= 0)
     34 		return Rect(0,0,0,0);
     35 
     36 	if(truetoboundingbox)
     37 		return r;
     38 
     39 	/* initdraw not called yet, can't use %R */
     40 	if(chatty) fprint(2, "[%d %d %d %d] -> ", R(r));
     41 	/*
     42 	 * attempt to sniff out A4, 8½×11, others
     43 	 * A4 is 596×842
     44 	 * 8½×11 is 612×792
     45 	 */
     46 
     47 	a = Dx(r)*Dy(r);
     48 	if(a < 300*300){	/* really small, probably supposed to be */
     49 		/* empty */
     50 	} else if(Dx(r) <= 596 && r.max.x <= 596 && Dy(r) > 792 && Dy(r) <= 842 && r.max.y <= 842)	/* A4 */
     51 		r = Rect(0, 0, 596, 842);
     52 	else {	/* cast up to 8½×11 */
     53 		if(Dx(r) <= 612 && r.max.x <= 612){
     54 			r.min.x = 0;
     55 			r.max.x = 612;
     56 		}
     57 		if(Dy(r) <= 792 && r.max.y <= 792){
     58 			r.min.y = 0;
     59 			r.max.y = 792;
     60 		}
     61 	}
     62 	if(chatty) fprint(2, "[%d %d %d %d]\n", R(r));
     63 	return r;
     64 }
     65 
     66 #define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y
     67 
     68 int
     69 prefix(char *x, char *y)
     70 {
     71 	return strncmp(x, y, strlen(y)) == 0;
     72 }
     73 
     74 /*
     75  * document ps is really being printed as n-up pages.
     76  * we need to treat every n pages as 1.
     77  */
     78 void
     79 repaginate(PSInfo *ps, int n)
     80 {
     81 	int i, np, onp;
     82 	Page *page;
     83 
     84 	page = ps->page;
     85 	onp = ps->npage;
     86 	np = (ps->npage+n-1)/n;
     87 
     88 	if(chatty) {
     89 		for(i=0; i<=onp+1; i++)
     90 			print("page %d: %d\n", i, page[i].offset);
     91 	}
     92 
     93 	for(i=0; i<np; i++)
     94 		page[i] = page[n*i];
     95 
     96 	/* trailer */
     97 	page[np] = page[onp];
     98 
     99 	/* EOF */
    100 	page[np+1] = page[onp+1];
    101 
    102 	ps->npage = np;
    103 
    104 	if(chatty) {
    105 		for(i=0; i<=np+1; i++)
    106 			print("page %d: %d\n", i, page[i].offset);
    107 	}
    108 
    109 }
    110 
    111 Document*
    112 initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
    113 {
    114 	Document *d;
    115 	PSInfo *ps;
    116 	char *p;
    117 	char *q, *r;
    118 	char eol;
    119 	char *nargv[1];
    120 	char fdbuf[20];
    121 	char tmp[32];
    122 	int fd;
    123 	int i;
    124 	int incomments;
    125 	int cantranslate;
    126 	int trailer=0;
    127 	int nesting=0;
    128 	int dumb=0;
    129 	int landscape=0;
    130 	long psoff;
    131 	long npage, mpage;
    132 	Page *page;
    133 	Rectangle bbox = Rect(0,0,0,0);
    134 
    135 	if(argc > 1) {
    136 		fprint(2, "can only view one ps file at a time\n");
    137 		return nil;
    138 	}
    139 
    140 	fprint(2, "reading through postscript...\n");
    141 	if(b == nil){	/* standard input; spool to disk (ouch) */
    142 		fd = spooltodisk(buf, nbuf, nil);
    143 		sprint(fdbuf, "/dev/fd/%d", fd);
    144 		b = Bopen(fdbuf, OREAD);
    145 		if(b == nil){
    146 			fprint(2, "cannot open disk spool file\n");
    147 			wexits("Bopen temp");
    148 		}
    149 		nargv[0] = fdbuf;
    150 		argv = nargv;
    151 	}
    152 
    153 	/* find %!, perhaps after PCL nonsense */
    154 	Bseek(b, 0, 0);
    155 	psoff = 0;
    156 	eol = 0;
    157 	for(i=0; i<16; i++){
    158 		psoff = Boffset(b);
    159 		if(!(p = Brdline(b, eol='\n')) && !(p = Brdline(b, eol='\r'))) {
    160 			fprint(2, "cannot find end of first line\n");
    161 			wexits("initps");
    162 		}
    163 		if(p[0]=='\x1B')
    164 			p++, psoff++;
    165 		if(p[0] == '%' && p[1] == '!')
    166 			break;
    167 	}
    168 	if(i == 16){
    169 		werrstr("not ps");
    170 		return nil;
    171 	}
    172 
    173 	/* page counting */
    174 	npage = 0;
    175 	mpage = 16;
    176 	page = emalloc(mpage*sizeof(*page));
    177 	memset(page, 0, mpage*sizeof(*page));
    178 
    179 	cantranslate = goodps;
    180 	incomments = 1;
    181 Keepreading:
    182 	while(p = Brdline(b, eol)) {
    183 		if(p[0] == '%')
    184 			if(chatty) fprint(2, "ps %.*s\n", utfnlen(p, Blinelen(b)-1), p);
    185 		if(npage == mpage) {
    186 			mpage *= 2;
    187 			page = erealloc(page, mpage*sizeof(*page));
    188 			memset(&page[npage], 0, npage*sizeof(*page));
    189 		}
    190 
    191 		if(p[0] != '%' || p[1] != '%')
    192 			continue;
    193 
    194 		if(prefix(p, "%%BeginDocument")) {
    195 			nesting++;
    196 			continue;
    197 		}
    198 		if(nesting > 0 && prefix(p, "%%EndDocument")) {
    199 			nesting--;
    200 			continue;
    201 		}
    202 		if(nesting)
    203 			continue;
    204 
    205 		if(prefix(p, "%%EndComment")) {
    206 			incomments = 0;
    207 			continue;
    208 		}
    209 		if(reverse == -1 && prefix(p, "%%PageOrder")) {
    210 			/* glean whether we should reverse the viewing order */
    211 			p[Blinelen(b)-1] = 0;
    212 			if(strstr(p, "Ascend"))
    213 				reverse = 0;
    214 			else if(strstr(p, "Descend"))
    215 				reverse = 1;
    216 			else if(strstr(p, "Special"))
    217 				dumb = 1;
    218 			p[Blinelen(b)-1] = '\n';
    219 			continue;
    220 		} else if(prefix(p, "%%Trailer")) {
    221 			incomments = 1;
    222 			page[npage].offset = Boffset(b)-Blinelen(b);
    223 			trailer = 1;
    224 			continue;
    225 		} else if(incomments && prefix(p, "%%Orientation")) {
    226 			if(strstr(p, "Landscape"))
    227 				landscape = 1;
    228 		} else if(incomments && Dx(bbox)==0 && prefix(p, q="%%BoundingBox")) {
    229 			bbox = rdbbox(p+strlen(q)+1);
    230 			if(chatty)
    231 				/* can't use %R because haven't initdraw() */
    232 				fprint(2, "document bbox [%d %d %d %d]\n",
    233 					RECT(bbox));
    234 			continue;
    235 		}
    236 
    237 		/*
    238 		 * If they use the initgraphics command, we can't play our translation tricks.
    239 		 */
    240 		p[Blinelen(b)-1] = 0;
    241 		if((q=strstr(p, "initgraphics")) && ((r=strchr(p, '%'))==nil || r > q))
    242 			cantranslate = 0;
    243 		p[Blinelen(b)-1] = eol;
    244 
    245 		if(!prefix(p, "%%Page:"))
    246 			continue;
    247 
    248 		/*
    249 		 * figure out of the %%Page: line contains a page number
    250 		 * or some other page description to use in the menu bar.
    251 		 *
    252 		 * lines look like %%Page: x y or %%Page: x
    253 		 * we prefer just x, and will generate our
    254 		 * own if necessary.
    255 		 */
    256 		p[Blinelen(b)-1] = 0;
    257 		if(chatty) fprint(2, "page %s\n", p);
    258 		r = p+7;
    259 		while(*r == ' ' || *r == '\t')
    260 			r++;
    261 		q = r;
    262 		while(*q && *q != ' ' && *q != '\t')
    263 			q++;
    264 		free(page[npage].name);
    265 		if(*r) {
    266 			if(*r == '"' && *q == '"')
    267 				r++, q--;
    268 			if(*q)
    269 				*q = 0;
    270 			page[npage].name = estrdup(r);
    271 			*q = 'x';
    272 		} else {
    273 			snprint(tmp, sizeof tmp, "p %ld", npage+1);
    274 			page[npage].name = estrdup(tmp);
    275 		}
    276 
    277 		/*
    278 		 * store the offset info for later viewing
    279 		 */
    280 		trailer = 0;
    281 		p[Blinelen(b)-1] = eol;
    282 		page[npage++].offset = Boffset(b)-Blinelen(b);
    283 	}
    284 	if(Blinelen(b) > 0){
    285 		fprint(2, "page: linelen %d\n", Blinelen(b));
    286 		Bseek(b, Blinelen(b), 1);
    287 		goto Keepreading;
    288 	}
    289 
    290 	if(Dx(bbox) == 0 || Dy(bbox) == 0)
    291 		bbox = Rect(0,0,612,792);	/* 8½×11 */
    292 	/*
    293 	 * if we didn't find any pages, assume the document
    294 	 * is one big page
    295 	 */
    296 	if(npage == 0) {
    297 		dumb = 1;
    298 		if(chatty) fprint(2, "don't know where pages are\n");
    299 		reverse = 0;
    300 		goodps = 0;
    301 		trailer = 0;
    302 		page[npage].name = "p 1";
    303 		page[npage++].offset = 0;
    304 	}
    305 
    306 	if(npage+2 > mpage) {
    307 		mpage += 2;
    308 		page = erealloc(page, mpage*sizeof(*page));
    309 		memset(&page[mpage-2], 0, 2*sizeof(*page));
    310 	}
    311 
    312 	if(!trailer)
    313 		page[npage].offset = Boffset(b);
    314 
    315 	Bseek(b, 0, 2); /* EOF */
    316 	page[npage+1].offset = Boffset(b);
    317 
    318 	d = emalloc(sizeof(*d));
    319 	ps = emalloc(sizeof(*ps));
    320 	ps->page = page;
    321 	ps->npage = npage;
    322 	ps->bbox = bbox;
    323 	ps->psoff = psoff;
    324 
    325 	d->extra = ps;
    326 	d->npage = ps->npage;
    327 	d->b = b;
    328 	d->drawpage = psdrawpage;
    329 	d->pagename = pspagename;
    330 
    331 	d->fwdonly = ps->clueless = dumb;
    332 	d->docname = argv[0];
    333 	/*
    334 	 * "tag" the doc as an image for now since there still is the "blank page"
    335 	 * problem for ps files.
    336 	 */
    337 	d->type = Tgfx;
    338 
    339 	if(spawngs(&ps->gs, "-dSAFER") < 0)
    340 		return nil;
    341 
    342 	if(!cantranslate)
    343 		bbox.min = ZP;
    344 	setdim(&ps->gs, bbox, ppi, landscape);
    345 
    346 	if(goodps){
    347 		/*
    348 		 * We want to only send the page (i.e. not header and trailer) information
    349 	 	 * for each page, so initialize the device by sending the header now.
    350 		 */
    351 		pswritepage(d, ps->gs.gsfd, -1);
    352 		waitgs(&ps->gs);
    353 	}
    354 
    355 	if(dumb) {
    356 		fprint(ps->gs.gsfd, "(%s) run PAGEFLUSH\n", argv[0]);
    357 		fprint(ps->gs.gsfd, "(/dev/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n");
    358 		close(ps->gs.gsfd);
    359 	}
    360 
    361 	ps->bbox = bbox;
    362 
    363 	return d;
    364 }
    365 
    366 static int
    367 pswritepage(Document *d, int fd, int page)
    368 {
    369 	Biobuf *b = d->b;
    370 	PSInfo *ps = d->extra;
    371 	int t, n, i;
    372 	long begin, end;
    373 	char buf[8192];
    374 
    375 	if(page == -1)
    376 		begin = ps->psoff;
    377 	else
    378 		begin = ps->page[page].offset;
    379 
    380 	end = ps->page[page+1].offset;
    381 
    382 	if(chatty) {
    383 		fprint(2, "writepage(%d)... from #%ld to #%ld...\n",
    384 			page, begin, end);
    385 	}
    386 	Bseek(b, begin, 0);
    387 
    388 	t = end-begin;
    389 	n = sizeof(buf);
    390 	if(n > t) n = t;
    391 	while(t > 0 && (i=Bread(b, buf, n)) > 0) {
    392 		if(write(fd, buf, i) != i)
    393 			return -1;
    394 		t -= i;
    395 		if(n > t)
    396 			n = t;
    397 	}
    398 	return end-begin;
    399 }
    400 
    401 static Image*
    402 psdrawpage(Document *d, int page)
    403 {
    404 	PSInfo *ps = d->extra;
    405 	Image *im;
    406 
    407 	if(ps->clueless)
    408 		return convert(&ps->gs.g);
    409 
    410 	waitgs(&ps->gs);
    411 
    412 	if(goodps)
    413 		pswritepage(d, ps->gs.gsfd, page);
    414 	else {
    415 		pswritepage(d, ps->gs.gsfd, -1);
    416 		pswritepage(d, ps->gs.gsfd, page);
    417 		pswritepage(d, ps->gs.gsfd, d->npage);
    418 	}
    419 	/*
    420 	 * If last line terminator is \r, gs will read ahead to check for \n
    421 	 * so send one to avoid deadlock.
    422 	 */
    423 	write(ps->gs.gsfd, "\n", 1);
    424 	fprint(ps->gs.gsfd, "\nPAGEFLUSH\n");
    425 	im = convert(&ps->gs.g);
    426 	if(im == nil) {
    427 		fprint(2, "fatal: readimage error %r\n");
    428 		wexits("readimage");
    429 	}
    430 	waitgs(&ps->gs);
    431 
    432 	return im;
    433 }
    434 
    435 static char*
    436 pspagename(Document *d, int page)
    437 {
    438 	PSInfo *ps = (PSInfo *) d->extra;
    439 	return ps->page[page].name;
    440 }