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